A while ago I ranted about how .NET properties didn’t play nice with delegates. The gist was that, if you had a property and a method which took a delegate of the appropriate type, you had to write your own new delegate method, even though the property’s getter and setter would ordinarily qualify. Here’s an example:
public class Foo {
public String Bar { get; set; }
}
public delegate String GetString();
public void display(GetString getString){
Console.WriteLine(getString());
}
public void performOnAValue(Action <String> action){
action("A Value");
}
Foo foo = new Foo { Bar = "bar" };
// You can't do this, because .NET doesn't recognize
// Foo.Bar.get() and Foo.Bar.set() as valid methods
// performOnAValue(foo.Bar); // foo.Bar.set() satisfies Action<String>
// display(Foo.Bar); // foo.Bar.get() satisfies GetString
// Instead you have to do this in .NET 2.0:
performOnAValue(delegate(String value) {
foo.Bar = value;
});
display(delegate() {
return foo.Bar;
});
Unfortunately, .NET 3.5 doesn’t fix this. However, it does give us enough new features that we can fake it. Consider this:
public static class Getter {
public static T get<T>(this T target) {
return target;
}
}
This extension method adds a get() method to every type. That means that we can do this:
display(foo.Bar.get<String>);
Even though Getter.get() doesn’t satisfy the GetString() signature (since it takes a <T>), .NET is smart enough to figure out that if you call it as an extension it will work.
The bad news is that you do have to specify the type of the object as the generic type parameter of get(). You could write a non-generic version that gets and returns an Object, but that leads to the potential for InvalidCastExceptions. The good news is that if you do use the wrong generic type parameter (let’s say foo.Bar.get<int>), the compiler will complain (albeit with a confusing error message).
More bad news: there’s no equivalent extension method for the setter. Without resorting to reflection, there’s no way (that I am aware of) to programmatically access an arbitrary property of an object from within a method. However, lambda expressions make the pain of writing a delegate a little easier to bear:
performOnAValue(value => foo.Bar = value);
You can also do the same for the getter, if you use an empty input parameter list and an inferred return keyword:
display( () => foo.Bar );
I’m not sure whether the lambda or the extension method getter is less ugly.
With these options, I’d say that the Stupid .NET Trick of properties and delegates is about 75% solved. The syntax is not as clean as it could be, but it’s a big step forward… possibly enough to make properties worth it again.
I think your blog has swallowed some greater-than less-than signs in the latter half of the essay.
Comment by Ted — November 20, 2007 @ 8:19 pm
Thanks… fixed that.
Comment by Craig — November 20, 2007 @ 8:26 pm
[...] методы, расширение методов и свойств объектов, свойства и делегаты. Без сомнения, очень полезно для .NET-разработчиков, [...]
Pingback by Веб-обзор #3: и снова 6 интересных статей для вечернего прочтения. | Alpha-Beta-Release B — November 22, 2007 @ 1:37 pm
The extension method isn’t a true function pointer. The value of the property is retrieved when the pointer is created (“foo.Bar.get”), and THAT VALUE is ALWAYS returned from that function pointer. The pointer using a lambda expression will properly retrieve the new value of the property.
Test Code:
public class FooClass { public String Bar { get; set; } }
public static class Getter { public static T get(this T target) { return target; } }
class Program
{
static void Main(string[] args)
{
FooClass foo = new FooClass();
foo.Bar = “First String”;
Func propertyPointer = foo.Bar.get;
Debug.WriteLine(String.Format(“Property Pointer returns: ‘{0}’”, propertyPointer()));
foo.Bar = “Second String”;
Debug.WriteLine(String.Format(“Property Pointer returns: ‘{0}’”, propertyPointer()));
foo.Bar = “Third String”;
Func lambdaPointer = (() => foo.Bar);
Debug.WriteLine(String.Format(“Lambda Pointer returns: ‘{0}’”, lambdaPointer()));
foo.Bar = “Fourth String”;
Debug.WriteLine(String.Format(“Lambda Pointer returns: ‘{0}’”, lambdaPointer()));
}
Output:
Property Pointer returns: ‘First String’
Property Pointer returns: ‘First String’
Lambda Pointer returns: ‘Third String’
Lambda Pointer returns: ‘Fourth String’
Comment by David Yaw — May 1, 2009 @ 1:23 pm