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 为我再次执行此操作。谢谢!
答案 0 :(得分:2)
好吧,如果你想坚持使用方法签名(string
:List<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)));
}
另一种甚至更好的方法是通过......像成员表达式而不是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";
}