-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Shared Immutable Properties #3831
Comments
I'm not sure this proposal would be worthwhile on its own. Instead, what could make sense is a more comprehensive solution for working with immutable types or objects. (See some previous discussion at #2543.) |
IMO an interface describes a contract of what the implementing type can do, not what it can't. It wouldn't make sense for the interface to dictate that a setter doesn't exist anymore that it would for any arbitrary method to not exist. |
I really don't think declaring that some arbitrary method does not exist really is comparable to declaring that a property doesn't have a setter. Properties might happen to be implemented via methods, but their purpose is to retrieve a value, not to do something. Whether a given parameter can change at any time is relevant to a significant number of scenarios in which you might want to consume it. As it currently stands, unless your interface is internal, and you have complete control over every single implementation of it, the only safe way to consume an interface is to assume that every property on it is mutable. Sure, you can write documentation to tell people implementing that interface not to make a given property mutable, but you could also write documentation to tell people not to use the setter on a property, or not to change a field, and still we have Interface properties are the only way to define values common to different structs, or any classes which need multiple-inheritance (or that you can't change the base class for). Mutability is fundamental to how you handle all sorts of things like threading, mapping, caching, etc. Interfaces are a set of member definitions (and now, sometimes, implementations) to provide common functionality. Whether or not a value is mutable is part of the definition of a value. |
I think you're reading more into what an interface is/does than what it
actually does.
An interface isn't specifying the implementation detail of a class - it is
saying that something tagged with that interface will provide the following
exposed components.
How their are achieved is up to each class.
For instance, your example of a property with a getter could be implemented
in one class as an auto property, in a second class with an explicit
backing field and some clever checking on the setter, and in a third class
as a computed getter based on the combination of two other properties that
aren't even on the interface.
The interface isn't saying "this class contains a readonly value" - it is
saying that it exposes a getter, and I can't see there being much desire to
change how interfaces have always worked.
…On Mon, 31 Aug 2020, 23:33 Isaac Beizsley, ***@***.***> wrote:
IMO an interface describes a contract of what the implementing type can
do, not what it can't. It wouldn't make sense for the interface to dictate
that a setter doesn't exist anymore that it would for any arbitrary method
to not exist.
I really don't think declaring that some arbitrary method does not exist
really is comparable to declaring that a property doesn't have a setter.
Properties might happen to be implemented via methods, but their purpose
is to retrieve a value, not to *do* something. Whether a given parameter
can change at any time is relevant to a significant number of scenarios in
which you might want to consume a value.
As it currently stands, unless your interface is internal, and you have
complete control over every single implementation of it, the only safe way
to consume an interface is to assume that every property on it is mutable.
Sure, you can write documentation to tell people implementing that
interface not to make a given property mutable, but you could also write
documentation to tell people not to use the setter on a property, or not to
change a field, and still we have readonly fields and get-only properties.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#3831 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADIEDQMQAWF32YREDIH3QDLSDQQJ5ANCNFSM4QQ56PTA>
.
|
@iBeizsley
Except that's exactly what it is.
It's up to the implementation to determine what a property does or how it does it. The getter accessor method may mutate the value by itself. Even if an interface could prevent a type from providing a setter accessor that wouldn't prevent the type from providing some other mutator method.
I am not challenging any aspect of these statements. I would like to see more and better immutability support in the language and runtime. I don't think that interfaces are the place to try to dictate them. |
However, the above doesn't force hte thing you want. i.e. it doesn't force anything to be immutable. You could still override a get-only property (or method), and absolutely have it mutate. |
I'm aware; that's what the part in brackets states. I'm really just pointing out the weird inconsistency there. On abstract classes, you can't define a setter on a property which doesn't have a setter defined on its base. For interfaces, you can. While you can get around that (as in it doesn't actually guarantee immutablility), the actual way to declare an immutable property on an abstract class is the same for a normal class... You don't make it virtual, and don't give it a setter. Anyway: The only argument against this seems to be that interfaces 'just aren't the place for this', but I don't really see the alternative...
An Depending on how the immutability implementation works, you might be able to have a property on the immutable class which contains the mutable fields for that class. The other way round won't work, because you'll need a property to expose the immutable member wrapper, and that property has no guarantee of immutability when consuming it via an interface. Shapes might work, but unless you can declare fields on shapes, you're back to the same old problem (unless you can declare properties as immutable for all implementations, you can't declare something as actually immutable... and if you can declare a property as immutable for all of its implementations, then why not on an interface?). Perhaps I've framed this all wrong, but it's really a question of whether you can be explicit in defining whether a property is immutable. On a struct, or a class where you don't make the property virtual, you can; not declaring a setter declares the property as immutable. But the only time you can share the fact that a property is immutable is when you have a base class which has a non-virtual property with only a getter on it, hence the focus on interfaces... Because interfaces are the preferred way of defining a common set of members, and the only way of doing so in many scenarios. Unless you're going to invent a new way of having a common set of values which works just like an interface, except you can declare properties on it as immutable, then nothing will solve this... And that seems like a vastly inferior option to just letting you do it on an interface. |
Even if you could have your Say that this is valid: public interface IFoo
{
immutable int Bar { get; }
} While I could implement it like this: public class Baz : IFoo
{
public int Bar { get; }
public Baz(int startingValue) => Bar = startingValue;
} There is still nothing to stop me implementing it like this: public class Fail : IFoo
{
public int Bar => new Random().Next(1, 101);
} I still meet the requirements of the interface - I've not declared a setter, but the output of the property still isn't what I would consider "immutable". |
The compiler can stop you implementing it like that. As mentioned in the OP, there's an obvious progression for implementing such a descriptor. The simplest is to only allow auto-properties to implement an However, for explicit get implementations:
Could do this bullet-by-bullet, or chunk the first three, or do it all at once. Each point is only a nice-to-have, though the first four, at least, are pretty important if there are real use-cases for side-effects not changing the prop's return value (not convinced, personally). While this is primarily focused on interfaces, there's no reason you couldn't also allow Down the road, proper pure methods could pretty much just build on this logic, and then be allowed in |
After testing
|
To be clear, the only reason that we didn't allow you to expand that init to a full set in the class implementation is due to clr limitations, not due to any moral desires around enforcing intent. If we could have allowed implementation 3 in your examples, we would have. |
Closed as the auto-implementation only version of this has actually been implemented in C# 9's init props, and the manual implementations were sugar for unknown use-cases.
Updated to describe implementation & the why better, reflecting comments on 05/09/2020
When declaring a virtual or abstract (for brevity, will simply be defined as virtual from here on out) property, be it on a class or an interface, it is currently impossible to express that the property should be immutable.
When you declare a non-virtual property, or a sealed property, you can do this:
All of the above properties are immutable. There is no way to change their values (barring reflection, but maybe not on .NET Core 3+ I think?) once they are initialized. However, a property only having a getter is never a good indication that it is immutable.
This means that for anybody to know whether a given property is actually immutable or not, they must know that the property is either auto-implemented, or pointing at constants, pure methods, other immutable properties, or readonly fields at its final implementation.
This is important for all kinds of reasons, but the most common ones are parallelism and mapping (what if you want to use
Bar
orFoo
as a dictionary key?). Knowing that a value is immutable is incredibly handy. If you don't know that a value will always be immutable, however, you need to handle its mutability in many situations... This means unnecessary locking, event handlers for a property important to a mapping being changed, etc.The only way you can enforce an immutable value on a shared contract currently is to have access to its base class, and be able to mark the value as sealed, and point it at an immutable value (or auto-implement it with only a getter). Interfaces are out, which means structs and a huge swathe of classes cannot guarantee immutability for their values in a shared context.
Enter
immutable
(or any other keyword) for properties:Not tied to the syntax; the
immutable
keyword could bereadonly
, or ano set
could be used, or anything else.There is a fairly obvious, incremental implentation path for this feature. The simplest is to only allow auto-properties when implementing an
immutable
property. This pretty much solves the problem (I'm sure there are edge cases which could be addressed (i.e side-effects irrelevant to the return value), but you don't lose anything with this implementation).However, for explicit get implementations:
sealed
props, or props onsealed
classes overriding a virtual property on their base.Could do this bullet-by-bullet, or chunk the first three, or do it all at once. Each point is only a nice-to-have, though the first four, at least, are pretty important if there are real use-cases for side-effects not changing the prop's return value (not convinced, personally).
To use a method in an explicit get implementation, we would need to know that the method is pure. The compiler does not check purity, and relying on an attribute when everything else is checked at compile time seems like a bad idea. However, this implementation actually lays a lot of the groundwork for compile-time checking pure methods.
Collisions between properties declaring a setter and properties explicitly not declaring a setter on interfaces can be handled in the same way as any other interface collision:
As a final note, pretty much all of the responses have been negative so far, but with the hold-up being the ability to effectively declare the lack of a method on an interface. Interfaces just aren't the place for this, right?
I do not see the alternative.
If you really hate the idea of interfaces being able to do this, please actually suggest any better solution (or argue that this isn't a problem at all). The actual description of interfaces in the documentation is as follows:
There is nothing in the documentation that I can see explicitly ruling anything like this out. Interfaces are, as of C#8, no longer mere lightweight contracts. They can declare default implementations. Being able to declare a functionality of
provides this immutable value
is far less in conflict with the idea of light-weight behavioural contracts than plenty of things interfaces can already do... Interfaces can provide values. The mutability of a value is vital information when consuming that value. The argument is pretty much "we shouldn't be able to do this because we can't currently do this".The text was updated successfully, but these errors were encountered: