Mark some properties and methods as `readonly`
See original GitHub issueBackground and motivation
Some properties and methods should be marked readonly. This way they can be used on readonly references without causing defensive copies.
This is something I ran into recently with Padding, so I went and found (hopefully) all of the types which could be improved by marking them / some of their members as readonly. I have an analyser which detected a defensive copy for my use of Padding, and I have currently worked around it by using Unsafe.AsRef (obviously I could have also accepted the defensive copy) - but I would like to remove this workaround as it should be unnecessary.
Note that Point, PointF, etc. have already had this done to them in https://github.com/dotnet/corefx/pull/40407.
I would like to make a PR for this when approved.
API Proposal
namespace System.Drawing
{
//`: IEquatable<CharacterRange>` is currently not in ref-only dll, but is in source dll:
- public struct CharacterRange
+ public struct CharacterRange : IEquatable<CharacterRange>
{
- public int First { get; set; }
+ public int First { readonly get; set; }
- public int Length { get; set; }
+ public int Length { readonly get; set; }
- public override bool Equals(object? obj);
+ public override readonly bool Equals(object? obj);
//`bool Equals(CharacterRange other)` is currently not in ref-only dll, but is in source dll:
+ public readonly bool Equals(CharacterRange other);
- public override int GetHashCode();
+ public override readonly int GetHashCode();
//...
}
}
namespace System.Windows.Forms
{
- public struct BindingMemberInfo
+ public readonly struct BindingMemberInfo
{
public string BindingPath { get; }
public string BindingField { get; }
public string BindingMember { get; }
public override bool Equals(object? otherObject);
public bool Equals(BindingMemberInfo other);
public override int GetHashCode();
//...
}
//or we could make each member readonly instead? (applicable members listed above if this is what we do)
- public struct ImeModeConversion
+ public readonly struct ImeModeConversion
{
//...
}
public struct LinkArea
{
- public int Start { get; set; }
+ public int Start { readonly get; set; }
- public int Length { get; set; }
+ public int Length { readonly get; set; }
- public bool IsEmpty { get; }
+ public bool IsEmpty { readonly get; }
- public override bool Equals(object? o);
+ public override readonly bool Equals(object? o);
- public bool Equals(LinkArea other);
+ public readonly bool Equals(LinkArea other);
- public override string ToString();
+ public override readonly string ToString();
- public override int GetHashCode();
+ public override readonly int GetHashCode();
//...
}
public struct Message
{
- public int Msg { get; set; }
+ public int Msg { readonly get; set; }
- public IntPtr WParam { get; set; }
+ public IntPtr WParam { readonly get; set; }
- public IntPtr LParam { get; set; }
+ public IntPtr LParam { readonly get; set; }
- public IntPtr Result { get; set; }
+ public IntPtr Result { readonly get; set; }
- public object? GetLParam(Type cls);
+ public readonly object? GetLParam(Type cls);
- public override bool Equals(object? o);
+ public override readonly bool Equals(object? o);
- public bool Equals(Message other);
+ public readonly bool Equals(Message other);
- public override int GetHashCode();
+ public override readonly int GetHashCode();
- public override string ToString();
+ public override readonly string ToString();
//...
}
public struct Padding
{
- public int All { get; set; }
+ public int All { readonly get; set; }
- public int Bottom { get; set; }
+ public int Bottom { readonly get; set; }
- public int Left { get; set; }
+ public int Left { readonly get; set; }
- public int Right { get; set; }
+ public int Right { readonly get; set; }
- public int Top { get; set; }
+ public int Top { readonly get; set; }
- public int Horizontal { get; }
+ public int Horizontal { readonly get; }
- public int Vertical { get; }
+ public int Vertical { readonly get; }
- public Size Size { get; }
+ public Size Size { readonly get; }
- public override bool Equals(object? other);
+ public override readonly bool Equals(object? other);
- public bool Equals(Padding other);
+ public readonly bool Equals(Padding other);
- public override int GetHashCode();
+ public override readonly int GetHashCode();
- public override string ToString();
+ public override readonly string ToString();
//...
}
public struct TableLayoutPanelCellPosition
{
- public override bool Equals(object? other);
+ public override readonly bool Equals(object? other);
- public bool Equals(TableLayoutPanelCellPosition other);
+ public readonly bool Equals(TableLayoutPanelCellPosition other);
- public override string ToString();
+ public override readonly string ToString();
- public override int GetHashCode();
+ public override readonly int GetHashCode();
//...
}
}
Pre-existing attributes omitted for clarity.
Edit: Removed TableLayoutPanelCellPosition.Row, TableLayoutPanelCellPosition.Column, Message.HWnd, and all of the properties on TextMetrics, as these were already marked as readonly due to being auto properties.
Edit 2: Add implementation of IEquatable<CharacterRange> to CharacterRange + its method, since it’s declared in source, but not in the ref project.
Edit 3: Add missing Padding.GetHashCode() and Padding.ToString() members.
Edit 4 (question): Should we fix the NotSupported.cs’s definition of CharacterRange to have the correct fields. It doesn’t really matter, but would better match what C# would generate for a ref assembly.
API Usage
The applicable APIs can now be used anywhere that these types are declared as readonly without causing a defensive copy, thus improving performance.
e.g. (a simplified version of my scenario)
struct SomeLargeStruct
{
public Padding Padding;
//...
}
int SomeMethod(in SomeLargeStruct referenceToABunchOfFields, int index)
{
//some condition
if (condition) return referenceToABunchOfFields.Padding.Top;
}
Alternative Designs
For BindingMemberInfo, we could do:
namespace System.Windows.Forms
{
public struct BindingMemberInfo
{
- public string BindingPath { get; }
+ public readonly string BindingPath { get; }
- public string BindingField { get; }
+ public readonly string BindingField { get; }
- public string BindingMember { get; }
+ public readonly string BindingMember { get; }
- public override bool Equals(object? otherObject);
+ public override readonly bool Equals(object? otherObject);
- public bool Equals(BindingMemberInfo other);
+ public bool readonly Equals(BindingMemberInfo other);
- public override int GetHashCode();
+ public override readonly int GetHashCode();
}
}
instead of
namespace System.Windows.Forms
{
- public struct BindingMemberInfo
+ public readonly struct BindingMemberInfo
{
public string BindingPath { get; }
public string BindingField { get; }
public string BindingMember { get; }
public override bool Equals(object? otherObject);
public bool Equals(BindingMemberInfo other);
public override int GetHashCode();
}
}
Risks
This should be pretty uncontroversial.
Maybe I have missed an API somewhere, I’m pretty sure I got all the public structs, but I may have missed a nested one or something like that.
Will this feature affect UI controls?
I would hope not.
Issue Analytics
- State:
- Created 2 months ago
- Reactions:2
- Comments:10 (10 by maintainers)

Top Related StackOverflow Question
We don’t have to do the formal API review. I’m going to try and find some time tomorrow to sanity check this. We can never remove
readonlyso I want to take another look to make sure I’ve thought it through a bit for each of these.@hamarb123 I’ll leave the PR to you if you have time. Let me know if you want me to jump in and get it done.