csharplang
                                
                                 csharplang copied to clipboard
                                
                                    csharplang copied to clipboard
                            
                            
                            
                        readonly members on interfaces
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:
public interface IVector<TVector> where TVector : struct, IVector<TVector>
{
    readonly TVector Add(in TVector other);
}
TVector Add(in TVector a, in TVector b) where TVector : struct, IVector<TVector>
{
    return a.Add(b); // shouldn't cause copying
}
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, if Vector contains 4 float values, Vector<Vector> then contains 8 values, Vector<Vector<Vector>> 16 values; each containing two fields of the inner type:
public readonly struct Vector<TInner> where TInner : struct, IVector<TInner>
{
    readonly TInner first;
    readonly TInner second;
    public readonly Vector<TInner> Add(in Vector<TInner> other)
    {
        return new Vector<TInner>(first.Add(other.first), second.Add(other.second)));
    }
}
Without readonly on IVector<TVector>.Add, this code must copy first and second before calling Add on the inner type, and there is no other way without abandoing readonly and in at all to prevent this, other than providing a separate object for operations with TVector Add(in TVector, in TVector) method, at the cost of a virtual call. Marking the interface method readonly means that the compiler knows the in 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 honor readonly as well. Then, if the implementing type is a struct, it must implement any readonly method with another readonly method. The other way around should be possible though, i.e. implementing a non-readonly method by a readonly 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 of this, so in essence all methods are implicitly externally readonly. A class can therefore freely implement an interface with readonly methods without caring about readonly anywhere in the implementation, because this will never be modified. A readonly 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++-style const 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 a readonly method doesn't have to care about upholding readonly on that particular method, because its can never be differentiated from the point of the interface.
I believe readonly members would need to be allowed on class types before this could happen.
@DaZombieKiller That might not be necessary. There is no real benefit to a readonly class or a class method, as it doesn't have to be pure anyway, and there is no such thing as a readonly object or reference to it. So readonly can be specifiable on classes with the same rules as for structs, or not, and then you would be able to implement a readonly interface method with a non-readonly one in a class.
I am for the second option. If people start placing readonly on classes or their methods because they think the code is safer or faster that way, that only shows a misunderstanding of the keyword. Make it explicit that having a readonly interface member is there only for implementing structs, and classes could ignore it.
Sounds like the request is roughly the following:
- Allow the readonlymodifier to appear oninterfacemember declarations.
- When a structimplements aninterfacewhere members have thereadonlymodifier, the matching methods on thestructmust also havereadonly. Or the declaration must be areadonly structwhich impliesreadonlyon every member
- When a readonlymember is invoked on a value which is a type parameter than is also constrained to be astructthen elide copies where you would elide them forreadonlymembers on astruct.
That's a reasonable extension of the readonly feature. It's also a request that I've seen before (with slightly different semantics). I'm unsure if there are enough places that this would be useful to justify doing it.
Consider that when we did the readonly struct feature originally we talked about allowing individual members also be marked as readonly. That was not done though, in part, because we couldn't find enough types / data to justify adding that extra level of granularity. After we released readonly struct though developers provided enough examples that we felt it was worth the extra level of granularity and concept count to the language.
I think we'd need to see similar examples or patterns to get this through LDM.
I believe readonly members would need to be allowed on class types before this could happen.
Not necessarily. The readonly member feature is specific to struct types. The rules could just be written to ignore readonly when implementing the interface on a non-struct type.
@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.
I believe readonly members would need to be allowed on class types before this could happen.
Not necessarily. The
readonlymember feature is specific tostructtypes. The rules could just be written to ignorereadonlywhen implementing theinterfaceon a non-struct type.
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.
@DavidArno
IMO readonly already means something other than "pure" in the C# language so I doubt any such feature would be based on that keyword.
@DavidArno
The whole concept of readonly is to prevent a location from being modified. readonly structs and methods are here to allow operating on read-only data without having to create a defensive copy each time, as the compiler must ensure that even though it produces unverifiable code (loading the address of a read-only field), the code is still valid. There is no such thing for classes – the object's mutability is governed purely by its implementation, and having a readonly (private) field in a class or readonly method is as useful as having a readonly local variable or a parameter – to make sure that you don't modify it.
There is nothing like a readonly object neither in the language nor in the runtime, there is no mechanism for producing the defensive copy of an object, and most importantly, readonly can be completely bypassed if you move your class's storage (to another object or a dictionary, for example).
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 readonly.
There is nothing like a readonly object neither in the language nor in the runtime...
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.
I doubt that we would ever add readonly to class types in the future because it really has no inherent value. The value proposition for struct is quantifiable: type states implementation detail as contract so consumers can avoid introducing unnecessary defensive copies. The value proposition for class would be: type states implementation detail. Not really a big seller.
The reason is that readonly is a shallow guarantee. That's true even for struct as a struct nested in another struct is really just putting a name on a group of fields. The readonly of the containing struct ends up dominating the design of the nested struct. That has the bonus though that we can avoid defensive copies with a single readonly on the containing type.
The shallow guarantee for a class though doesn't really provide any value. What does it matter if a method implementation in a class modifies it's immediate field vs. a nested field? To the consumer it's still a modification and there is no specific optimizations or really any semantic changes you could make based on that information.
If we added a modifier to class it is much more likely to be immutable. That is essentially the readonly guarantee but deep., Meaning an immutable type can only contain other types which are immutable. That is a meaningful guarantee that consumers can optimize around.
Just an example of how readonly on classes can be bypassed.
readonly class Class
{
    readonly StorageClass storage = new StorageClass();
    public int Property {
        get {
            return storage.Property;
        }
        set {
            storage.Property = value;
        }
    }
    class StorageClass
    {
        public int Property;
    }
}
@IllidanS4,
Just an example of how readonly on structs can be bypassed:
    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 readonly types to be used within a readonly class. But this would create inconsistencies with readonly structs and the latter could still be used to bypass the restriction via the above example.
As @jaredpar mentioned, a new keyword - eg immutable will likely be needed for types and fields to indicate that not only is the reference immutable, but the entire type - all the way down - is immutable too. Just as it sometimes makes sense now to mark a non-read-only struct's methods as readonly, so that existing keyword could be used to denote that a class method doesn't access mutable state either.
So therefore, introducing readonly methods  to interfaces before the whole subject of immutable types has been thought through risks making immutable types more difficult to implement.
Thinking about it, externally readonly method means "this method will not modify the variable where it is called". In this sense, all class methods are readonly as the this reference itself will never be modified.
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 arg shall never be modified, so under the hood it creates a defensive copy. However, this doesn't have to happen in two cases: if T is constrained to a class, so arg is an object reference and thus can be modified only by direct assignment to arg; or if IFormattable.ToString is declared readonly. If both are true, readonly is superfluous, so technically all reference types are already readonly (in the terms of references to their instances). This also means that if where T : readonly is introduced in the future, it should match readonly structs but also all classes.
Just an example of how readonly on structs can be bypassed:
This doesn't bypass readonly on a struct though. The readonly modifier, both on the struct declaration and in storage locations such as fields, describes the state of the memory / location of the fields. Specifically that the location cannot be altered through that reference.
The readonly modifier was never intended to protect against modifications to reference types that were reachable from a struct. That is true all the way back to C# 1.0.
Saying that this approach bypasses readonly requires an incorrect definition of the feature.
While this might be logically plausible. @DavidArno concern is valid. I think the idea that keyword is already there but ignoring class is a bit concerned me
So if readonly interface member need to be implement anyway. I think this might be better if we wait for shape and allow us to define readonly shape as a constraint instead? That way we don't need to change the old interface to support this feature
But then again, Maybe we could just use another keyword (maybe new one or repurpose the old one) for pure class and member, and left readonly as it is, or making readonly a super/sub keyword of that another keyword (readonly will ignore class but that another keyword work on both class and struct)
I think we should leave the concept of purity or the actual soundness of readonly to other proposals. The aim of this is simple: to be able to accept readonly values that implement a specific contract simply and efficiently. So something like this would be finally possible, as a result:
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
}
I just hit this as well. Seems like a simple, worthwhile feature. It basically means I can provide my users the following API:
public class C
{
  public void M<T>(in T t) where T : IMyInterface
  {
      t.Run();
   }
}
public interface IMyInterface
{
  readonly void Run();
}
and then the callers of C.M won't copy if the input is a struct. The whole point of providing in in the first place is to avoid copying, but as far as I can tell there's no way to avoid it right now without readonly interface methods.
As a hack you can use "System.Runtime.CompilerServices.Unsafe.AsRef" to avoid copies - but it is unsafe - you can call non readonly methods...
using System;
var f = new F()
{
    Value = 12
};
C.M(in f);
Console.WriteLine(f.Value);
public class C
{
  public static void M<T>(in T t) where T : IMyInterface
  {
      ref T tr = ref System.Runtime.CompilerServices.Unsafe.AsRef(in t);
      tr.Run();
      tr.Run2(); //this violates "in" - t is modified
   }
}
public interface IMyInterface
{
   void Run();
   void Run2();
   public int Value { get; set; }
}
public struct F : IMyInterface
{    
    public readonly void Run(){
    }
    public void Run2(){
        Value++;
    }
    public int Value { get; set; }
}
readonly interface members would be great for structs - but what would that mean for implementing classes (there we have no readonly methods, too)?
readonly interface members would be great for structs - but what would that mean for implementing classes (there we have no readonly methods, too)?
I have said that already in the first post: a readonly method can be easily implemented in a normal class without any specific keyword, because (externally) all methods on a class are implicitly readonly.
Perhaps better to illustrate with an example:
interface I
{
    readonly void M();
}
struct S : I
{
    public C c;
    public readonly void M()
    {
        c.M();
    }
}
class C
{
  void M();
}
S.M can be readonly, because it does not change the reference when C.M is called. But all that S contains is a reference to C, so it should be possible to simply use C in the first place without an intermediate struct. Calling C.M on any reference never changes the immediate value (the reference itself) which is what readonly is about.
@agocke
I just hit this as well. Seems like a simple, worthwhile feature.
It's simple if you just consider the current landscape of readonly. That is that it only applies to struct. There are several proposals out there to have readonly apply to class in a similar fashion. Taking this proposal first essentially decides, in the negative way, about ever doing readonly on class. At the point readonly exists in interface it becomes a massive compat issue to start applying readonly to class as it would break exsisting interface implementations.
@jaredpar Frankly I still don't see any compatibility issue here.
The meaning of readonly is fixed for structs and their methods ‒ externally it signalizes that the method won't change the value of this (making it safe to use for readonly references) and internally it prevents you from modifying the fields of the struct.
On interfaces, readonly cannot prevent you from modifying any of the fields (because there are none). What absolutely remains to be decided though is how default interface methods should interact with it. I assume this is boxed there, so readonly methods should be allowed to call non-readonly methods freely (unless there is a difference between this.M(); and var c = this; c.M(); ‒ what type would c be to prevent you from calling c.M?).
I don't really see why or how a class could uphold readonly on an interface in any way that is sensible to someone who uses it through that interface. What would the "value" of an interface's instance be anyway? All values of its properties? Just have a property that always evaluates to Guid.NewGuid() and you'll have a "mutable" state without breaking any possible readonly constraint anyway, unless C# turns into a pure functional language.
The proposals for readonly classes have been left undecided for years, so why not decide at least something which actually makes two parts of the language usable together? I say classes should completely ignore any readonly attributes placed on their interfaces. Show me why they shouldn't.
@IllidanS4
Frankly I still don't see any compatibility issue here.
The effect of readonly is that it forces implementing members to have readonly if applicable. Today for struct that has meaning and prescribes a specific behavior. This proposal ignores class because readonly is not a valid modifier.
It's possible in the future that readonly becomes a valid class declaration and member modifier. There have been many such proposals for that over the years in a couple different forms. If that proposal was ever implemented then suddenly readonly has meaning for class and it becomes a compat issue for any class that implemented a interface with one or more readonly members. At the very least it's a source breaking change when that happens.
This is something that has to be thought through and considered. This is why I reject the idea that it's a simple feature to implement.
@jaredpar What exactly does it mean that "this proposal ignores class"? Both in the initial post and in my two previous posts I have given an example how this could be easily resolved without interfering with any class readonly proposal. It's simple ‒ just ignore readonly on interfaces when implemented by a class, now and in the future, because that makes the most sense. I've stated this over and over yet somehow I forget that classes exist.
It's an irony that two years ago the "blocker" for this was, for some reason, that readonly on classes was not considered:
I doubt that we would ever add
readonlytoclasstypes in the future because it really has no inherent value.
Yet now the issue is that readonly on classes is considered and has priority over this proposal. All while I keep repeating that they are independent on each other.
@IllidanS4
Yet now the issue is that readonly on classes is considered and has priority over this proposal. All while I keep repeating that they are independent on each other.
It's not about priority, it's about the fact that they are dependent on one another.  Implementing readonly on interface members today certainly impacts the ability for the language team to define what it would mean for readonly to be applied to class members, or whether the former would require the latter.  The team can't "just ignore it" as it effectively forces their hand on other decisions, and clearly they're not comfortable simply dropping one for the other.
It's simple ‒ just ignore readonly on interfaces when implemented by a class, now and in the future
This is not palatable to me. Esp. if we ever actually have readonly classes. Takign this approach seriously limits future design space. And we do not want to limit future design space unless we're completely sure as a team that that is very much what we want.
So why or how should a class take readonly on an interface into account? Interfaces don't have any fields or inner workings that even could be protected with readonly.
So why or how should a class take readonly on an interface into account?
We'd have to design that out.
Interfaces don't have any fields or inner workings that even could be protected with readonly.
Sure. But classes would. So potentially to respect that, a class might have to not mutate itself in any of tehse members.
So potentially to respect that, a class might have to not mutate itself in any of tehse members.
How is that useful to users of the interface? I cannot distinguish whether a class mutated itself or not from the perspective of an interface. And in any case, I can perform a number of tricks ranging from another inner instance to ConditionalWeakTable that bypasses this. readonly on an interface is useless for a class.
This is something that has to be thought through and considered. This is why I reject the idea that it's a simple feature to implement.
Agreed, I forgot about language evolution questions.
How is that useful to users of the interface?
There are many times i would like to enforce that a type isn't mutating out from underneath me, and having such a constraint would be valuable, even for classes.
readonly on an interface is useless for a class.
Under your set of value propositions that may be true. But we may evaluate under a different set. And those value propositions may change over time.
I can perform a number of tricks ranging from another inner instance
I don't know what this means. I could envision a world, for example, where being readonly might put such a constraint on inner values as well, for deep readonlyness to a type and enforcing restrictions to make mutation potentially impossible (deeply).
There are many times i would like to enforce that a type isn't mutating out from underneath me, and having such a constraint would be valuable, even for classes.
I agree, but this is not what readonly is about. We are arguing readonly here and that keyword is for shallow immutability with respect to the fields, not for any other meaning. We've already established that, were this the case, there would be a keyword like immutable with this meaning, as using readonly for that would simply be confusing.
But we may evaluate under a different set. And those value propositions may change over time.
This set is already affected by the existing meaning of readonly for fields and structs/struct methods. Just with the key difference that those aren't hypothetical features; they actually exist.
I don't know what this means. I could envision a world, for example, where being readonly might put such a constraint on inner values as well
We don't have that world and we can't have that world ‒ readonly already means a specific thing and breaking that would be much more horrendous than the slight inconvenience for class readonly proposals this one would pose.
We can't have this discussion if we can't agree what readonly actually means. Could we please stick to what the current version of C# uses it for?