如何重用使用Reflection获取的属性

时间:2016-10-26 05:29:55

标签: c# generics reflection

private static string GenerateStr<T>(IList<T> obj, string propName)
{
  string str = string.Empty;
  foreach(var o in obj)
  {
      str += o.GetType().GetProperty(propName).GetValue(o, null); 
          //is there a way to only call above line once, then call
          // str =+ o.myStrProp over the course of the iteration?

  }
  return str;
}

有没有办法能够重复使用已获取的属性,以避免依赖 Reflection 为我再次执行此操作。谢谢!

3 个答案:

答案 0 :(得分:2)

你实际要求的是什么

好吧,如果你想坚持使用方法签名(stringList<T>string)那么你至少可以像这样重用被提取的PropertyInfo:< / p>

private static string GenerateStr<T>(IEnumerable<T> obj, string propName)
{
    var propertyInfo = typeof(T).GetProperty(propName);
    string str = string.Empty;

    foreach(var o in obj)
    {
        str += propertyInfo.GetValue(o, null);
    }

    return str;
}

这甚至可以使用LINQ缩短:

private static string GenerateStr<T>(IEnumerable<T> list, string propName)
    {
        var propertyInfo = typeof(T).GetProperty(propName);

        return string.Concat(list.Select(o => propertyInfo.GetValue(o, null)));
    }

IMO更好的方法

另一种甚至更好的方法是通过......像成员表达式而不是string作为第二个参数:

private string GenerateStrBetter<T>(IEnumerable<T> list, Func<T, object> func)
{
    var res = string.Empty;

    foreach (var item in list)
    {
        res += func(item).ToString();
    }

    return res;
}

这应该更快,因为它根本不使用反射。此外,它可以重写为一行;):

private string GenerateStrBetter<T>(IEnumerable<T> list, Func<T, object> func)
{
    return string.Concat(list.Select(item => func(item).ToString()));
}

用法

var result = GenerateStrBetter(list, item => item.Text);

这不仅速度更快,它还可以在开发过程中通过应用IntelliSense和避免魔术字符串为您提供支持,一旦有人重构您的属性名称,就会忘记迁移。

答案 1 :(得分:1)

这个受thibaud60启发的例子创建了委托,与每次迭代使用的反射相比,这是非常快的。

所以你要做的就是在循环开始之前调用:var accessor = PropertyHelper.CreateAccessor(typeof(T).GetProperty(propName));。要获得价值,您只需拨打var value = accessor.GetValue(o);

即可
public static class PropertyHelper
{
    public static IPropertyAccessor CreateAccessor(PropertyInfo propertyInfo)
    {
        if (propertyInfo == null)
            throw new ArgumentNullException("propertyInfo");

        return (IPropertyAccessor)Activator.CreateInstance(
            typeof(PropertyWrapper<,>).MakeGenericType
                (propertyInfo.DeclaringType, propertyInfo.PropertyType), propertyInfo);
    }
}

public interface IPropertyValueAccessor
{
    PropertyInfo PropertyInfo { get; }

    string Name { get; }
    object GetValue(object source);
}

public interface IPropertyAccessor
{
    PropertyInfo PropertyInfo { get; }

    string Name { get; }
    object GetValue(object source);
    void SetValue(object source, object value);
}

internal class PropertyWrapper<TObject, TValue> : IPropertyAccessor
{
    private PropertyInfo _propertyInfo;
    private Func<TObject, TValue> _getMethod;
    private Action<TObject, TValue> _setMethod;

    /// <summary>
    /// Constructeur public
    /// </summary>
    /// <param name="propertyInfo">la propriété à encapsulé
    public PropertyWrapper(PropertyInfo propertyInfo)
    {
        _propertyInfo = propertyInfo;

        MethodInfo mGet = propertyInfo.GetGetMethod(true);
        MethodInfo mSet = propertyInfo.GetSetMethod(true);

         // Rq : on peut par se biais acceder aussi aux accesseur privé
        //      tous les aspects liés à la sécurité est donc pris en charge par CreateDelegate
        //      et non à chaque appel à GetMethod/SetMethod

        _getMethod = (Func<TObject, TValue>)Delegate.CreateDelegate
                (typeof(Func<TObject, TValue>), mGet);
        _setMethod = (Action<TObject, TValue>)Delegate.CreateDelegate
                (typeof(Action<TObject, TValue>), mSet);
    }

    object IPropertyValueAccessor.GetValue(object source)
    {
        return _getMethod((TObject)source);
    }
    void IPropertyAccessor.SetValue(object source, object value)
    {
        _setMethod((TObject)source, (TValue)value);
    }

    /// <summary>
    /// Voir <see cref="IPropertyAccessor.Name">
    /// </see></summary>
    public string Name
    {
        get
        {
            return _propertyInfo.Name;
        }
    }

    /// <summary>
    /// Voir <see cref="IPropertyAccessor.PropertyInfo">
    /// </see></summary>
    public PropertyInfo PropertyInfo
    {
        get
        {
            return _propertyInfo;
        }
    }
}

答案 2 :(得分:1)

这里有几个选项 - 第一个是使用反射,但不是在每次迭代中检索属性:

static string GenerateStrReflection<T>(IList<T> obj, string propName)
{
    var property = typeof(T).GetProperty(propName);

    return string.Concat(obj.Select(o => property.GetValue(o)));
}

请注意,使用String.Concat比使用+=运算符更快,更节省内存的方法来组合字符串,因为每次迭代都会创建一个新字符串!

如果要在大型列表上执行时进一步提高此方法的速度,可以考虑编译Lambda表达式:

static string GenerateStrExpression<T>(IList<T> obj, string propName)
{
    // o
    var oParameter = Expression.Parameter(typeof(T), "o");
    // o.Property
    var propertyExpression = Expression.PropertyOrField(oParameter, propName);
    // cast to object ensure we don't get compiler errors when creating the lambda
    var cast = Expression.Convert(propertyExpression, typeof(object));
    // o => (object)o.Property;
    var lambda = Expression.Lambda<Func<T, object>>(cast, oParameter).Compile();

    return string.Concat(obj.Select(lambda));
}

这适用于大型列表,但Compile方法的开销非常大(请参阅下面的数字)。通过让我们的函数返回一个编译函数来对列表进行操作,我们可以确保Compile只发生一次:

static Func<IList<T>, string> GenerateStrExpressionCached<T>(string propName)
{
    var oParameter = Expression.Parameter(typeof(T), "o");
    var propertyExpression = Expression.PropertyOrField(oParameter, propName);
    var cast = Expression.Convert(propertyExpression, typeof(object));
    var lambda = Expression.Lambda<Func<T, object>>(cast, oParameter).Compile();
    // here we return a lambda to operate against the list.
    return list => string.Concat(list.Select(lambda));
}

这允许我们缓存该函数,例如:

var cachedFunc = GenerateStrExpressionCached<MyClass>("MyProperty");
List<MyClass> myList = ...;
string result = cachedFunc(myList);

为了提供一些证据,下面的数字用于1000模式的Release次迭代(优化代码)。如您所见,列表大小是方法更快的一个重要因素。请注意,第3列有点欺骗,因为它在执行时间内没有考虑对GenerateStrExpressionCached<MyClass>("MyProperty")的调用。

List Size      Reflection (ms)     Expression (ms)   Expression Cached (ms)
1              0                   127               0
100            20                  133               4
10000          2000                600               470

测试代码:

const int ListSize = 10000; //change to what you want to measure
const int Iterations = 1000;
var list = new List<MyClass>(ListSize);
for (var i = 0; i < ListSize; i++)
    list.Add(new MyClass());

//initialize the cached function
var cachedFunc = GenerateStrExpressionCached<MyClass>("MyProperty");

var sw = Stopwatch.StartNew();

for (var i = 0; i < Iterations; i++)
    GenerateStrExpression(list, "MyProperty");
Console.WriteLine(sw.ElapsedMilliseconds);

sw.Restart();
for (var i = 0; i < Iterations; i++)
    GenerateStrReflection(list, "MyProperty");
Console.WriteLine(sw.ElapsedMilliseconds);

sw.Restart();
for (var i = 0; i < Iterations; i++)
    cachedFunc(list);
Console.WriteLine(sw.ElapsedMilliseconds);

...

class MyClass
{
    public string MyProperty { get; } = "Hello World";
}