简体   繁体   中英

Is there any way to pass the setter of a property to a delegate?

I know this question has already been asked, but this time I have two additional constraints:

  1. Reflection cannot be used.

  2. I don't want to pass a wrapper around a property's setter. I want to pass the setter itself:

     // NO. NO. NO; myObject.MyMethod(value => anotherObject.AnotherProperty = value);

The answer to your question is going to be no. At least as far as C# is concerned.

Why? Let's say that you have the following object:

public class Foo
{
    public int SomeProp { get; set; }
}

you know that under the hood the compiler will autogenerate int get_SomeProp() and void set_SomeProp(int value) for you (plus the backing field), so you should be able to do this:

var someMethod = foo.get_SomeProp;

and you can - almost. You get this error from the compiler: "cannot explicitly call operator or accessor". So without reflection or a wrapper, you're SOL. Maybe. I say maybe because just because C# doesn't let you treat a getter or setter like a real method, it doesn't mean that some other .NET language can't. For example, I can write this in F#:

namespace PassSetter

module mucker =
    let muckWithFoo (aFoo:Foo) =
        aFoo.set_SomeProp

and now muckWithFoo is a function declared as Foo->(int->unit), which is equivalent to a method that returns delegate void d(int value). In essence, you can use another module to break the C# compiler constraint, if need be. I picked F# only because I have the compiler handy, but I bet you could do this with C++/CLI as well.

The main difference between this and a wrapper is that even though you still need to write a wrapper for every type for which you would like to get a delegate from, that wrapper doesn't end up attached to the final delegate.

I don't know what your issue is with the "no reflection" constraint - is it that you're running in an environment that forbids reflection or that you believe that you're so performance bound that you can't afford to use reflection. If it's the latter, then there are several more options open to you that give you much better performance than simply reflection to get the property set method and then invoke it (effectively call by name).

Define this helper function(You'll need to add the error checking):

Action<TObject,TValue> GetSetter<TObject,TValue>(Expression<Func<TObject,TValue>> property)
{
    var memberExp=(MemberExpression)property.Body;
    var propInfo=(PropertyInfo)memberExp.Member;
    MethodInfo setter=propInfo.GetSetMethod();
    Delegate del=Delegate.CreateDelegate(typeof(Action<TObject,TValue>),setter);
    return (Action<TObject,TValue>)del;
}

And use it like this:

Action<MyClass,int> setter=GetSetter((MyClass o)=>o.IntProperty);

This is not exactly what you want(It uses reflection), but probably as close as you'll get. The returned delegate is the setter itself, no wrapper.

Why not use an interface?

interface IPropertySetter {
  string Property { set; }
}

Have your class implement it:

class MyClass : IPropertySetter {
  public string Property { get; set; }
}

And pass it to your object:

var c = new MyClass();
myObject.MyMethod(c);

...

void MyMethod(IPropertySetter setter) {
  setter.Property = someValue;
  // you can also store the "setter" and use it later...
}

C# does not allow this (because of the specialname attribute the compiler generates on the set_AnotherProperty method), however J# would because it does not support the property syntax.

This is what the code would look like.

  Action<int> x = set_AnotherProperty(1);

However, the C# compiler tells you

Error   3   'Program.AnotherProperty.set': 
cannot explicitly call operator or accessor

I am pretty sure the answer is no. The most common approach to this is to use lambda expressions, see for example the common solution to hard-coded INotifyPropertyChanged strings:

http://10rem.net/blog/2010/12/16/strategies-for-improving-inotifypropertychanged-in-wpf-and-silverlight

There are no other magic ways!

A property can be wrapped in a delegate, if intellectual purity and lexical beauty are not synonymous. Of course it is not quite a property, with null being a trigger for read/write operations and the need for the 'called' to treat it like a function, after all.

xType1 xProperty1 {get;set;}

xType2 xProperty1 {get;set;}

xCallingFunction()
{
   ....
   xCalledFunction( 
     ((s)=> s == null ? xProperty1 : (xProperty1 = s)),
     ((s)=> s == null ? xProperty2 : (xProperty2 = s))
     );
   ....
}

....

xCalledFunction( Func<xType, xType> parm1, Func<xType2, xType2> parm2 )
   var p1 = parm1(null);
   parm2( newValue );
   parm1( parm1(null) + 16 );

If you don't want to pass a wrapper around a property's setter, you can go the other way around and let the setter be a wrapper around a method. For example:

public MyClass
{
    private Foo _myProperty;
    public Foo MyProperty
    {
        get { return _myProperty; }
        set { MyPropertySetter(value); }
    }

    public void MyPropertySetter(Foo foo)
    {
        _myProperty = foo;
    }
}

then do:

myObject.Mymethod(myOtherObject.MyPropertySetter);

It's not pretty, but it does what you want done.

For your constraints, it seems you can't use properties at all. If the performance considerations are so strong that you can't simply wrap the setter in a lambda, then you might want to change your style of development. That's normally what's done in extreme cases of code tuning. Put on your Java hat and create explicit setter and getter methods in your C# code :

class MyClass {
  private string _value;
  public void setProperty(string value) { _value = value; } 
} 

No properties involved... But, how much that really buys you over lambdas? I wouldn't think it's noticeable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM