readonly members on interfaces #8809
Replies: 45 comments
-
I believe |
Beta Was this translation helpful? Give feedback.
-
@DaZombieKiller That might not be necessary. There is no real benefit to a I am for the second option. If people start placing |
Beta Was this translation helpful? Give feedback.
-
Sounds like the request is roughly the following:
That's a reasonable extension of the Consider that when we did the I think we'd need to see similar examples or patterns to get this through LDM.
Not necessarily. The |
Beta Was this translation helpful? Give feedback.
-
@jaredpar Yes, that is pretty much it. Not sure about others, but I have stumbled upon this when doing what was pretty much my initial example. It would also give this issue a reason to be resolved. |
Beta Was this translation helpful? Give feedback.
-
This would create issues were readonly/pure classes ever to be implemented in the future. We'd have the situation where an interface could declare a readonly member, as could a class. But there'd either be no requirement for the class to respect the readonly requirement of the interface, or you'd have to make a massive breaking change to enforce that contract causing existing code to fail. |
Beta Was this translation helpful? Give feedback.
-
IMO |
Beta Was this translation helpful? Give feedback.
-
@DavidArno There is nothing like a Concepts like "read-only" and "pure" are independent on each other, and aren't even in any subset relation. Something to indicate mutability (the observable state of the object is not modified) or purity (the observable state of anything is not modified) could be useful in the future, but it's not related to |
Beta Was this translation helpful? Give feedback.
-
And there was nothing like a readonly struct before the concept was introduced. Immutable types (ie ones that go beyond having read-only class fields where the ref can't change to guaranteeing the content of the things pointed to by those refs are read-only too) would be a useful addition to the language. Likewise having read-only methods that do not change the (direct) value of the object's state would also be useful (you are right that these aren't quite pure methods as they cannot guarantee no side effects without truly immutable types). This might never happen of course as there are so many other features vying for the language team's attention. But it would be disappointing to see this effectively ruled out completely simply because a change was made to support structs that then rendered it so much harder to implement read-only/immutable classes in future. |
Beta Was this translation helpful? Give feedback.
-
I doubt that we would ever add The reason is that The shallow guarantee for a If we added a modifier to |
Beta Was this translation helpful? Give feedback.
-
Just an example of how readonly class Class
{
readonly StorageClass storage = new StorageClass();
public int Property {
get {
return storage.Property;
}
set {
storage.Property = value;
}
}
class StorageClass
{
public int Property;
}
} |
Beta Was this translation helpful? Give feedback.
-
Just an example of how readonly struct Struct
{
readonly StorageClass storage;
public int Property {
get {
return storage.Property;
}
set {
storage.Property = value;
}
}
class StorageClass
{
public int Property;
}
} As I previously mentioned, immutable types, that do not permit such bypassing, would be a useful addition to the language. That could be achieved by only allowing As @jaredpar mentioned, a new keyword - eg So therefore, introducing |
Beta Was this translation helpful? Give feedback.
-
Thinking about it, externally This is especially apparent with generics: public static string TestFormat<T>(in T arg) where T : IFormattable
{
return arg.ToString(null, null);
} The compiler has to ensure that the value of |
Beta Was this translation helpful? Give feedback.
-
This doesn't bypass The Saying that this approach bypasses |
Beta Was this translation helpful? Give feedback.
-
While this might be logically plausible. @DavidArno concern is valid. I think the idea that keyword is already there but ignoring So if But then again, Maybe we could just use another keyword (maybe new one or repurpose the old one) for |
Beta Was this translation helpful? Give feedback.
-
I think we should leave the concept of purity or the actual soundness of interface IBetterFormattable
{
readonly string ToString(string format);
}
static int GetSpecificStringValue<T>(this in T arg) where T : struct, IBetterFormattable // "struct" also shoudn't be technically needed
{
return arg.ToString("some format"); // no defensive copying for *any* input
} |
Beta Was this translation helpful? Give feedback.
-
That's the state of hte world now. You're asking me to make a stance that that will be the case forever.
|
Beta Was this translation helpful? Give feedback.
-
No. This hasn't been established. It's been argued for. That argument may end up winning out. It's certainly reasonable. But it's not been established fully as a design point that we will base all future designs off of. I, for one, would want to deeply (no pun intended) go into that sort of decision before stating firmly that it's certainly what we want. |
Beta Was this translation helpful? Give feedback.
-
We don't have that world... because we're talking about the present. I don't know where our future designs will take us.
I don't see why not. The future of hte language is ours to decide where we want to go with. The feature literally doesn't exist, and we can decide what we would want it to mean at the point it came into being.
There is no current meaning for 'readonly' on interfaces, or how it would affect classes. That's the entire point of "a design space". What readonly means in the future is an open topic that may develop and evolve with teh language. The team may decide it's acceptable to restrict taht design space. But it's not a given, and our general approach is to not do that until we've discussed it fully and believe that to be the right decision. Until that happens, the preference is to keep things open so that we don't end up overly restricting things and ending up in a situation we regret. |
Beta Was this translation helpful? Give feedback.
-
You are technically right ‒ something that is not defined today could easily be defined to be absolutely anything tomorrow, but practically, any definition lives surrounded by the already existing definitions, in this case how
Sure, I can envision that world as well, but readonlyness where defined now means something else. Going for this world would introduce either breaking changes or inconsistency. Neither seems desirable to me.
You are correct, and I respect this methodology. To me this discussion feels like a part of this decision process, so I hope this is worth it. |
Beta Was this translation helpful? Give feedback.
-
This effectively seems to boil down to one of these situations, should
Feel free to suggest other situations, if there are more. Case 2 and 3 seem most likely to me; case 4 is hard to imagine (but feel free to prove me otherwise) and case 1 seems too inconsistent to me to be followed with. If you want to have some sort of process to narrow down the possible cases, I urge you to have it, to start somewhere. |
Beta Was this translation helpful? Give feedback.
-
There is a legitimate source (and potentially binary) breaking consideration if a decision on how Consider: interface IFoo
{
readonly int GetLength();
} A struct implements this and everything is fine as the implementing method must be A class implements this but Adding Now consider that value types are sealed and the language has used This means that It is, IMO, highly probably that |
Beta Was this translation helpful? Give feedback.
-
This all depends on how you choose to interpret Foo f = ...; // Foo implements IFoo
Foo g = f;
f.GetLength();
// f and g are still identical This kind of contract really only makes sense for value types, because you do have access to their fields, and in situations like Now why do I choose this kind of interpretation? The answer is simple: consistency. A declaration like Remember that the purpose of using And if there is a need to add something similar, but not quite like, to
That is a valid concern, but it's more of a technical issue. Sure, adding
Not necessarily to never support it; see my previous post. This proposal does restrict how
Perhaps, but even this interpretation is completely irrelevant from the perspective of an interface, because making all of your class's fields |
Beta Was this translation helpful? Give feedback.
-
There is no "choosing" to interpret here as the spec and documentation has very explicitly defined semantics. Irrespective of what someone else observes, the spec wins out 9/10 (the exception generally being esoteric edge case bugs that have existed for 10+ years that can't be fixed due to back-compat). The language designers and compiler devs who work on, define, and implement the language features are effectively the "de facto" source of truth on ambiguities and interpretation. They are also the de-facto source for future direction, considerations, and impact that various things have. In the case of
It's been stated multiple times by the language designers and experts that this proposal being implemented without first considering what |
Beta Was this translation helpful? Give feedback.
-
Sure, but it has been pointed out before that something that is not yet defined is free to be defined in any conceivable way. I am just stating that if you are in that position to define a new occurrence of a keyword, it makes sense to extend the definition from other contexts. There are multiple ways to do that, so there is a freedom of choice there. To me
It is complicated, or at least there are multiple ways to do it. For one using a
I didn't say otherwise. Any feature added to the language restricts what additional features could be added, or how. Say you wanted to make
The next step then is to assess how big the impact would be. I don't argue that there is none, but I have already argued that |
Beta Was this translation helpful? Give feedback.
-
There are 428 active issues in the repo representing "championed" or "already implemented" language features. There are The language has many competing issues, priorities, and desires and tends to group things together into themes as well as making overall iterative improvements each release. Some issues can take a few years to get properly prioritized, designed, and implemented. However, even with moving at this pace, C# is still getting massive amounts of innovation each release and improving in leaps and bounds. It is also not moving slower than most other languages, in many cases its moving a bit faster now (especially compared to how it used to progress). These things take time and as much as you really want |
Beta Was this translation helpful? Give feedback.
-
It was not my intention to complain that C# is evolving too slowly, in fact I am grateful for the additions that are being added in, and the pace. The complaint is that the proposal for
GitHub discussions weren't a thing 2 years ago 😉. I am not opposing it, but this seems concrete enough. |
Beta Was this translation helpful? Give feedback.
-
and a respective member of LDM championing the issue and driving it to completion. Finding a champion is most often the first step
Yes, but triagers have moved most non-championed issues to be discussions. This one was likely either missed or its potentially incorrectly labeled. @333fred might be able to confirm and transfer if this should in fact be a discussion since it doesn't have a listed champion. |
Beta Was this translation helpful? Give feedback.
-
Unless @jaredpar or @CyrusNajmabadi would want to champion this, it should probably be moved to a discussion. |
Beta Was this translation helpful? Give feedback.
-
It would be surprising if |
Beta Was this translation helpful? Give feedback.
-
Indeed, that is why I think that |
Beta Was this translation helpful? Give feedback.
-
I think
readonly
members as introduced in C# 8.0 (#1710) should be usable on members of interfaces as well. The main reason is in this example:If
readonly
members on interfaces aren't available, there is no way to make a generic method for vector adding (or other operations) without copying. This is extremely useful once you start working with larger structures that are composed of multiple smaller types (like vectors and matrices). For example, ifVector
contains 4float
values,Vector<Vector>
then contains 8 values,Vector<Vector<Vector>>
16 values; each containing two fields of the inner type:Without
readonly
onIVector<TVector>.Add
, this code must copyfirst
andsecond
before callingAdd
on the inner type, and there is no other way without abandoingreadonly
andin
at all to prevent this, other than providing a separate object for operations withTVector Add(in TVector, in TVector)
method, at the cost of a virtual call. Marking the interface methodreadonly
means that the compiler knows thein
parameter will not be modified (at least by an implementation in C#).readonly
should be allowed on interface members to indicate that the implementing method must honorreadonly
as well. Then, if the implementing type is a struct, it must implement anyreadonly
method with anotherreadonly
method. The other way around should be possible though, i.e. implementing a non-readonly
method by areadonly
one.Relation to classes and #3885
Calling a
readonly
method on structs already has a well-defined meaning:this
won't be modified from inside of the method. This is the meaning that is extended here to interfaces, but classes cannot change the value ofthis
, so in essence all methods are implicitly externallyreadonly
. A class can therefore freely implement an interface withreadonly
methods without caring aboutreadonly
anywhere in the implementation, becausethis
will never be modified. Areadonly
variable of a reference type never requires creating a defensive copy when calling any of its methods, and is a non-issue if used in the example above.This proposal is, for the most part, unrelated to #3885, but what needs to be resolved is whether
readonly
on an interface should denote external readonlyness (this
is not observably modified) or internal readonlyness (can't write to fields and call non-readonly
methods). Value types share both meanings, because there is no indirection that could separate them. It seems that classes could only have internal readonlyness, unless a C++-styleconst
reference to an object is considered, but then it would warrant a new keyword anyway.For that reason, even if
readonly class
was added, the relation to interfaces would still be the same: a class implementing an interface with areadonly
method doesn't have to care about upholdingreadonly
on that particular method, because its can never be differentiated from the point of the interface.Beta Was this translation helpful? Give feedback.
All reactions