Delegated properties, like in Kotlin #1096
Replies: 13 comments 9 replies
-
@AsafMah This is probably yet another use-case for #107. Check other use-cases here #341. |
Beta Was this translation helpful? Give feedback.
-
I like the idea of delegation here. It seems like some pretty powerful patterns could emerge, but I dislike the proposed syntax. I feel like it's a missed opportunity to express a relationship between a type and a property on that type by enhancing generic constraints. The example code is opaque. |
Beta Was this translation helpful? Give feedback.
-
Eyalsk: that's the first thing that came to my mind as well! I would love to be able to do what is suggested here, but I think it could be solved in a better, more generic way. If we get some additional syntax to delegate properties, then it won't take long for "extended" suggestions to pop up (like maybe also proxying methods). That would bring us back to generators aka "replace original" again. |
Beta Was this translation helpful? Give feedback.
-
That generic way you speak of is source generation. Adding a specific syntax to the language just for that means you specialize not generalize and can't see why it warrants a new syntax. |
Beta Was this translation helpful? Give feedback.
-
Since the Source Generators feature seems to have gone nowhere I would like to resurrect this feature request... I've been teaching myself Kotlin recently and I found this feature to be both mind-blowing in concept and insanely useful. It's general enough to solve a few of the situations that users have been asking for language features for whilst also being easy to understand (both for users and tooling) and easy to develop for. For example, after just a few hours of learning Kotlin I was able to solve one of my pet peeves that C# has: Lazily initialised properties. import kotlin.reflect.*
class MyLazy<T : Any>(private val initializer : () -> T) {
var isInitialised = false
lateinit var value : T
operator fun getValue(receiver : Any?, property : KProperty<*>) : T {
if (!isInitialised) {
println("Initialising ${property.name}")
value = initializer()
isInitialised = true
}
return value
}
}
class A {
val someProp by MyLazy { "Hello world" }
}
fun main() {
println("Initialising a...")
var a = A()
println("...done")
println("a.someprop = ${a.someProp}")
println("a.someprop = ${a.someProp}")
} The code above prints:
I think this would be a fantastic feature for C# that has precedent and would allow for some controlled metaprogramming that will help users solve real-world problems. PS I'm aware that the Kotlin base library has an Lazy class for just this purpose already; I wanted to implement my own without looking at that to see how easy the delegates properties feature was to use. |
Beta Was this translation helpful? Give feedback.
-
Just to further reinforce the point: This remains the most useful feature of Kotlin, and the one I absolutely miss the most now that I work in C#. Just having a wrapper delegate over Unity's PlayerPrefs interface would make me so happy-- especially since C#'s initialization order makes |
Beta Was this translation helpful? Give feedback.
-
I have proposed something similar here #1095 . I hope more people find it useful and request for it. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I'm curious how a delegated property would work around the existing limitations in the C# language or if the language would require additional changes to support them. For instance, if the delegated property initialization was lifted to a field initializer you would run into the same problem with your There seems to be some interest from members of the LDG (@333fred) for a feature like this so perhaps it's worth trying to work through some implementation details to get it to a state that it can be championed and taken to LDM. |
Beta Was this translation helpful? Give feedback.
-
I am indeed interested in the idea of delegated properties, but I haven't yet come up with a proposal that I like. I'll post some of my notes and ruminations on the topic for general viewing though: Exploration: Delegated PropertiesThere are several proposals on dotnet/csharplang for enhancing the expressiveness of properties without falling back to a fully manual property syntax. @jnm2 has helpfully put together a gist tracking these (thanks @jnm2!), which can be found here. There are still other proposals for a more powerful enhancement: delegated properties. Motivation: What is a delegated property and why do I want one?At its core, a delegated property is a way of taking a common pattern used in propety implementation and abstracting it out, allowing it to be reused in multiple places. There are two signature examples that I used to inform this exploration, which are also some of the most common examples brought up when we look at proposals for the
There are, of course, many other types of patterns that you might want to abstract out for convenience. Miguel's wrapper proposal has a few examples, like logging or atomic implementation. This removes a bunch of boilerplate and allows the intent of the code to be more obvious to the reader. Types of delegated propertiesBefore we consider an explicit design for delegated properties, I think it's important that we try and categorize different types of delegated properties by what they need to work: StorageSome delegated properties require storage for an entirely new type, like ParameterizationAlmost every realistic delegated property example that I've been able to come up with needs some form of parameterization beyond just the value being set. The one example that I've seen that could be generally applicable is something like the A possible design for delegated properties in C#What follows in this section is a possible solution to introducing delegated properties in C#. My main concerns in this exploration were in allowing the expression of both storage-requiring and storage-non-requiring properties. I also mostly focused on helpers that require arbitrary-code callbacks, but I don't believe that there is a significant blocker in creating delegated properties that are simple wrappers. I did not, however, focus on one aspect from Miguel's proposal, namely composition over multiple of these wrappers. I believe that you could use the pieces in this exploration to create such a system without having to build in explicit support for it, and that it would fit better into C# as a whole. DefinitionI always like having an example of code to start with, and then dig into that example with explanations of how it works, so that's what I'm going to do here. Using the motivating examples from above, here's how you would implement public static class InterlockedLazy
{
public static TValue Get<TValue, TCallback>(ref TValue field, TCallback callback) where TCallback : ref struct, IInvocable<TValue>
{
if (field == default(TValue))
{
Interlocked.CompareExchange(ref field, callback(), default(TValue));
}
return field;
}
}
public struct Lazy<TValue>
{
private object _lock;
private TValue _value;
public void Get<TCallback>(TCallback callback) where TCallback : ref struct, IInvocable<TValue>
{
if (_value == default(TValue))
{
return _value;
}
lock (_lock)
{
if (_value == default(TValue))
{
return _value;
}
_value = callback();
_isInitialized = true;
return _value;
}
}
}
public static class NotifyPropertyChangedHelper
{
public void Set<TValue, TCallback>(ref TValue field, TValue newValue, TCallback callback, [CallerMemberName] string caller = "") where TCallback : ref struct, IInvocable<string>
{
if (field != newValue)
{
field = newValue;
callback(caller);
}
}
}
public static class Clamp
{
public void Set(ref int field, int newValue, int maxValue, int minValue, [CallerMemberName] string propertyName = "")
{
if (newValue > maxValue || newValue < minValue)
{
throw new InvalidArgumentException(propertyName);
}
field = newValue;
}
} The general pattern is, on any type Value delegatesIn exploring this space, several LDM members brought up a concern that they would like to see delegated properties give as little of a perf hit as possible. Part of this is expressed in this exploration by letting property wrappers be both UsageNow that we've seen how to define delegated property helpers, let's look at how to use them. Again, I'll start with examples of usage, and then explain how it works: public class C
{
private string MyExpensiveStringCreatingOp() { ... } // Omitted
public string LazyProp1 as InterlockedLazy<string>
{
get(MyExpensiveStringCreatingOp);
}
public string LazyProp2 as Lazy<string>
{
get(() => { ... /* Other expensive string operation, omitted */ });
}
public int ClampedProp as Clamp
{
get; set(-1, 1);
}
}
public record MyViewModel(int Prop1, int Prop2) : NotifyPropertyChangedBase /* Defines the event, and a helper to raise the event RaiseIfChanged */
{
int Prop1 as NotifyPropertyChangedHelper
{
get; set(RaiseIfChanged);
}
int Prop2 as NotifyPropertyChangedHelper
{
get; set(RaiseIfChanged);
}
} Here, we define a new part of a property declaration: the Thoughts on this approachThroughout this exploration, I've been careful to avoid calling it a proposal. It has the start of a proposal, but in the course of putting this together I've come to believe that full delegated properties, as seen here, are far too complicated and specific a topic to make it to 100 points for me. Rather, I think that we can achieve 90% of what this proposal offers by enabling a combination the the That's not to say there's nothing good about this exploration, and that we should throw out all of it. We've had some internal discussions around allowing the backing field of a property to be different from the type of the property (the catalyst for publishing this exploration, in fact). The current internal strawman is something like like: public string? P { get => field.Value; } = new Lazy<string?>(Compute); I've internally voiced disagreement with this approach, because using the assignment as a method of determining what the backing field type should be is brittle and lacks intent. However, we can take the Additionally, I think the concept of value delegates via ref interfaces is an interesting one that we should explore in detail. |
Beta Was this translation helpful? Give feedback.
-
I just want to give a few thoughts of mine here. I had this problem several times, but some problems that are discussed here only mostly deal with INotifyPropertyChanged which is (at least in my opinion only made for the connection to WPF property bindings). We are currently strictly following the MVVM pattern where we really always have a model and a viewmodel and a view. But our applications are also made such that I don't need a viewmodel and view at all, since most of our applications also need to run in command line mode. So we not only have MVVM but we also have some sort of an interprocess barrier between model and viewmodel. I my early days both, the model and the viewmodel implemented INotifyPropertyChanged where the viewmodel listened to the model. The problem was that INotifyPropertyChanged has only the property name in it which was in most cases insufficient for us. So we decided to use observables for our models (but it would of course also work with events). We chose observables because we also need to transfer other data to the viewmodel than only INotifyPropertyChanged. In the following example I will use the name DependencyObject (DO) which you already know from WPF (this is for a reason, I will explain later). So here are a few points what was our requirements: 1. Prevent repetitive code SetProperty<T>(T value, [CallerMemberName]string propertyName = null); and T GetProperty<T>([CallerMemberName]string propertyName = null);` which does the internal mechanics to store the properties and also check against already set values, because also here for performance reasons we didn't want to invoke unnecessary events if the value is already set. Inside there is a For a new class you then simply have to write public class NewObject : DependencyObject
{
public int Value1
{
get=> GetProperty<int>(10);
set=> SetProperty<int>(value);
}
public int Value2
{
get=> GetProperty<int>(10);
set=> SetProperty<int>(value);
}
} 2. Block the immediate transport of changed properties This was solved by putting a BeginUpdate/EndUpdate around property changed to lock the updates and could be done like this: NewObject DO = new();
DO.BeginUpdate();
DO.Value1 = value1;
DO.Value2 = value2;
DO.EndUpdate(); or by NewObject DO = new();
using DO.BeginUpdate();
DO.Value1 = value1;
DO.Value2 = value2; Of course you could also use NewObject DO = new();
using DO.BeginUpdate();
DO.SetProperty(value1, "Value1")
DO.SetProperty(value2, "Value2") One advantage of this was/is that you can also call CancelUpdate() to undo all changes, since it also has some mechanisms to store the current state if requested. And here is the point why INotifyPropertyChanged was mostly useless, because it only sent one property name or [Null] for a dramatic change, but it wasn't granular enough for our needs. We can also send the old value and the new value for each property so that we can react more on the changes. 3. Default values There's also a 4. Value coercion 5. Value binding 6. Value inheritance 7. Value generation All in all I don't want to tell what things we made but what things were necessary for us and while we were improving our own structures we found that this is WPF DependencyProperty which does all the work but unfortunately it's WPF only and uses the dispatcher internally which we cannot use in command line. So maybe having something like a DependencyObject (without WPF) would be cool. I don't think it's worth a language change but some more assistance to get around boxing problems might help. Especially where I need to store an in a Just some thoughts... |
Beta Was this translation helpful? Give feedback.
-
As the I have found that, the delegated properties are useful mainly in the cases when the backing field is of a different type from the property it is backing. In .net, usually we see the properties and the backing fields are of the same type. But sometimes it can be very useful to have a backing field of a different/complex generic wrapper type and then expose the property with getters and setters with a simple type. The first example of a wrapper type for the backing field would obviously be the So, I think, the delegated properties should be built upon this concept of generic wrapper types for properties, which implements some interfaces with getter/setter providers. If I borrow from Kotlin, the interfaces will be something like- public interface IReadOnlyProperty<T, V>
{
V GetValue(T thisRef, string propertyName);
}
public interface IReadWriteProperty<T, V> : IReadOnlyProperty<T, V>
{
new V GetValue(T thisRef, string propertyName);
void SetValue(T thisRef, string propertyName, V value);
} And any wrapper type implementing any of these two interfaces will be able to act as a "delegated property provider". class Person {
string Id by new Lazy<string>(/*init*/);
string Name by new Observable<string>(/*init*/);
} Was trying to put some high-level ideas about the proposal. Really want to see this proposal going forward in C#. |
Beta Was this translation helpful? Give feedback.
-
I really like this idea as I have seen this only today. But I dislike the syntax of the proposal. So I'd like to introduce my idea for a syntax for Delegated Properties. Basically the usage should be as simple as possible and also familiar. That's why I suggest following idea.
public class ObservableProperty<T> : System.Property
{
public T Get(object owner, ref T field, [CallerMemberName] string propertyName = "") { ... }
public void Set(object owner, ref T field, T value, [CallerMemberName] string propertyName = "") { ... }
} public string MyObservableProperty : ObservableProperty { get; set; }
public string MyLazyProperty : LazyProperty { get; set; } In general I would ignore the Property string of this. This is similar to Attributes and would look like this: public string MyObservableProperty : Observable { get; set; }
public string MyLazyProperty : Lazy { get; set; } The compiler could transform this into something like this pseudo code: private string <MyProperty>_field;
private ObservableProperty<string> <MyProperty>_delegate = new ObservableProperty<string>;
public string MyProperty
{
get
{
<MyProperty>_delegate.Get(this, ref <MyProperty>_field);
}
set
{
<MyProperty>_delegate.Set(this, ref <MyProperty>_field, value);
}
} This approach allows custom usage of the delegate as well to implement further stuff if really needed. public string MyProperty : Observable
{
get
{
Get(this, ref field, "MyProperty");
Console.WriteLine("MyProperty was read");
}
set
{
Set(this, ref field, value, "MyProperty");
Console.WriteLine("MyProperty was changed.");
}
} this would define In my opinion, this would not require changing the CLR. If need, there could be a default property, which would be implemented like this: public class DefaultProperty<T> : System.Property
{
public T Get(object owner, ref T field, [CallerMemberName] string propertyName = "") => field;
public void Set(object owner, ref T field, T value, [CallerMemberName] string propertyName = "") => field = value;
} full exampleAs example, I would give this, what would be already possible. public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
public class MainViewModel : INotifyPropertyChanged
{
private string _myString = "Hello, World!";
private readonly ObservableProperty<string> _myStringDelegate = new();
public string MyString
{
get => _myStringDelegate.Get(this, ref _myString);
set => _myStringDelegate.Set(this, ref _myString, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObservableProperty<T>
{
public T Get(object owner, ref T field, [CallerMemberName] string propertyName = "")
{
Debug.WriteLine($"We really like logging Get for {propertyName}.");
return field;
}
public void Set(object owner, ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return;
field = value;
if (owner is MainViewModel b) // Used MainViewModel for simplicity.
b.OnPropertyChanged(propertyName);
}
} This would change to the following making it much easier to write and also to read. public partial class MainWindow : Window
{
// Same as above
}
public class MainViewModel : INotifyPropertyChanged
{
public string MyString : Observable { get; set; } = "Hello, World!";
public event PropertyChangedEventHandler? PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObservableProperty<T>
{
// Same as above
} I would like some comments, if I missed something. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
When using frameworks it seems that there is a lot of boilerplate surrounding Properties.
Here are some examples:
INotifyPropertyChanged
It is used in both wpf and windows universal, which make a hefty percentage of the desktop GUI apps developed today, and it makes writing binded classes very annoying, and makes them less readable.
Someone already suggested special syntax just for this interface for how annoying it is:
https://github.com/dotnet/csharplang/issues/
I don't think we need new language features just for this interface, but the problem doesn't stop here.
ReactiveUI
This framework defines two types of special properties.
Both require a backing field and boilerplate:
The solution (In kotlin)
Kotlin has a very nice feature called "Delegated Properties".
You can read about it here: https://kotlinlang.org/docs/reference/delegated-properties.html
It looks like this:
p is an auto property (all kotlin properties are), but it is delegated to the delegate class.
The delegate class looks like this:
Basically, every time p is accessed, the corresponding method to the access type in Delegate would be called.
It lets you abstract over writing getters and setters, you only need to write the delegate class once, and now every property can use its functionality in one line.
The solution
What I propose is to add a mechanism similar to this in c#.
First, we would need to define a class.
It should have the methods GetValue and SetValue with the right signatures like this:
And that's it, now you have a re-useable way to have your classes support the interface.
the syntax for using the class could look like this (it's not final at all, of course):
As you can see it's pretty readable, convenient and extendable.
Other uses
The kotlin page lists other uses for this feature, notably their own version of Lazy - in kotlin, if you have a lazy property you can just define it to be your type T, instead of c#'s Lazy.
The delegated property will make sure that it is initialized late and only once, but when you use it you can treat it the same as any other string.
Beta Was this translation helpful? Give feedback.
All reactions