How can I pass a property as a delegate?

前端 未结 6 1537
深忆病人
深忆病人 2021-02-04 04:03

This is a theoretical question, I\'ve already got a solution to my problem that took me down a different path, but I think the question is still potentially interesting.

相关标签:
6条回答
  • 2021-02-04 04:47

    (Adding a second answer because it's on a completely different approach)

    To address your original problem, which is more about wanting a nice API for mapping named values in a datareader to properties on your object, consider System.ComponentModel.TypeDescriptor - an often overlooked alternative to doing reflective dirtywork yourself.

    Here's a useful snippet:

    var properties = TypeDescriptor.GetProperties(myObject)
        .Cast<PropertyDescriptor>()
        .ToDictionary(pr => pr.Name);
    

    That creates a dictionary of the propertydescriptors of your object.

    Now I can do this:

    properties["Property1"].SetValue(myObject, rdr["item1"]);
    

    PropertyDescriptor's SetValue method (unlike System.Reflection.PropertyInfo's equivalent) will do type conversion for you - parse strings as ints, and so on.

    What's useful about this is one can imagine an attribute-driven approach to iterating through that properties collection (PropertyDescriptor has an Attributes property to allow you to get any custom attributes that were added to the property) figuring out which value in the datareader to use; or having a method that receives a dictionary of propertyname - columnname mappings which iterates through and performs all those sets for you.

    I suspect an approach like this may give you the API shortcut you need in a way that lambda-expression reflective trickery - in this case - won't.

    0 讨论(0)
  • 2021-02-04 04:51

    As others have pointed, static reflexion is the way to go.

    Those classes work out of the box :

    http://www.codeproject.com/Articles/36262/Getting-Fun-with-Net-Static-Reflection.aspx

    0 讨论(0)
  • 2021-02-04 04:53

    I like using expression trees to solve this problem. Whenever you have a method where you want to take a "property delegate", use the parameter type Expression<Func<T, TPropertyType>>. For example:

    public void SetPropertyFromDbValue<T, TProperty>(
        T obj,
        Expression<Func<T, TProperty>> expression,
        TProperty value
    )
    {
        MemberExpression member = (MemberExpression)expression.Body;
        PropertyInfo property = (PropertyInfo)member.Member;
        property.SetValue(obj, value, null);
    }
    

    Nice thing about this is that the syntax looks the same for gets as well.

    public TProperty GetPropertyFromDbValue<T, TProperty>(
        T obj,
        Expression<Func<T, TProperty>> expression
    )
    {
        MemberExpression member = (MemberExpression)expression.Body;
        PropertyInfo property = (PropertyInfo)member.Member;
        return (TProperty)property.GetValue(obj, null);
    }
    

    Or, if you're feeling lazy:

    public TProperty GetPropertyFromDbValue<T, TProperty>(
        T obj,
        Expression<Func<T, TProperty>> expression
    )
    {
        return expression.Compile()(obj);
    }
    

    Invocation would look like:

    SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]);
    GetPropertyFromDbValue(myClass, o => o.Property1);
    
    0 讨论(0)
  • 2021-02-04 04:54

    Worth mentioning you can do this with some reflection trickery.. something like...

    public static void LoadFromReader<T>(this object source, SqlDataReader reader, string propertyName, string fieldName)
        {
            //Should check for nulls..
            Type t = source.GetType();
            PropertyInfo pi = t.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
            object val = reader[fieldName];
            if (val == DBNull.Value)
            {
                val = default(T);
            }
            //Try to change to same type as property...
            val = Convert.ChangeType(val, pi.PropertyType);
            pi.SetValue(source, val, null);
        }
    

    then

    myClass.LoadFromReader<string>(reader,"Property1","field1");
    
    0 讨论(0)
  • 2021-02-04 04:58

    Ignoring whether this is useful in your specific circumstances (where I think the approach you've taken works just fine), your question is 'is there a way to convert a property into a delegate'.

    Well, there kind of might be.

    Every property actually (behind the scenes) consists of one or two methods - a set method and/or a get method. And you can - if you can get a hold of those methods - make delegates that wrap them.

    For instance, once you've got hold of a System.Reflection.PropertyInfo object representing a property of type TProp on an object of type TObj, we can create an Action<TObj,TProp> (that is, a delegate that takes an object on which to set the property and a value to set it to) that wraps that setter method as follows:

    Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod())
    

    Or we can create an Action<TProp> that wraps the setter on a specific instance of TObj like this:

    Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod())
    

    We can wrap that little lot up using a static reflection extension method:

    public static Action<T> GetPropertySetter<TObject, T>(this TObject instance, Expression<Func<TObject, T>> propAccessExpression)
    {
        var memberExpression = propAccessExpression.Body as MemberExpression;
        if (memberExpression == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression");
    
        var accessedMember = memberExpression.Member as PropertyInfo;
        if (accessedMember == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression");
    
        var setter = accessedMember.GetSetMethod();
    
        return (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), instance, setter);
    }
    

    and now I can get a hold of a 'setter' delegate for a property on an object like this:

    MyClass myObject = new MyClass();
    Action<string> setter = myObject.GetPropertySetter(o => o.Property1);
    

    That's strongly typed, based on the type of the property itself, so it's robust in the face of refactoring and compile-time typechecked.

    Of course, in your case, you want to be able to set your property using a possibly-null object, so a strongly typed wrapper around the setter isn't the whole solution - but it does give you something to pass to your SetPropertyFromDbValue method.

    0 讨论(0)
  • 2021-02-04 04:59

    No, there's nothing akin to method group conversions for properties. The best you can do is to use a lambda expression to form a Func<string> (for a getter) or an Action<string> (for a setter):

    SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
                                   rdr["field1"]);
    
    0 讨论(0)
提交回复
热议问题