这个问题类似于this one ,但假设我们在编译时知道成员名称。
假设我们有一个类
public class MyClass
{
public string TheProperty { get; set; }
}
而在另一种方法中,我们想设置那个类的一个实例的TheProperty
成员,但是我们不知道编译时实例的类型,我们只知道属性名在编译时。
因此,在我看来,现在有两种方法可以做到这一点:
object o = new MyClass(); // For simplicity.
o.GetType().GetProperty("TheProperty").SetValue(o, "bar"); // (1)
((dynamic) o).TheProperty = "bar"; // (2)
我使用 System.Diagnostics.Stopwatch
类测量了这个测试用例,发现反射耗时 475 次 以及使用 dynamic< 的方式
花费了 0 个滴答声,因此与直接调用 new MyClass().TheProperty = "bar"
一样快。
由于我几乎没有见过第二种方式,所以我有点困惑,现在我的问题是:
- 有没有思路失误之类的?
- 第二种方式应该优于第一种方式还是相反?我没有看到使用第二种方式有任何缺点;如果找不到属性,(1) 和 (2) 都会抛出异常,不是吗?
- 为什么第二种方式看似更快,却很少被使用?
最佳答案
(...)reflection took 475 ticks and the way using dynamic took 0 ticks(...)
这完全是错误的。问题是您不了解 dynamic
的工作原理。我假设您正确设置了基准:
- 在 Release模式下运行并启用优化且没有调试器。
- 在实际测量时间之前,您正在放弃这些方法。
接下来是您可能没有做的关键部分:
- 在不实际执行动态运行时绑定(bind)的情况下停止动态测试。
为什么 3 很重要?因为运行时会缓存动态调用并重用它!因此,在一个简单的基准测试实现中,如果您做的事情是正确的,您将承担初始动态调用的成本,因此您不会对其进行测量。
运行以下基准测试:
public static void Main(string[] args)
{
var repetitions = 1;
var isWarmup = true;
var foo = new Foo();
//warmup
SetPropertyWithDynamic(foo, isWarmup); //JIT method without caching the dynamic call
SetPropertyWithReflection(foo); //JIT method
var s = ((dynamic)"Hello").Substring(0, 2); //Start up the runtime compiler
for (var test = 0; test < 10; test++)
{
Console.WriteLine($"Test #{test}");
var watch = Stopwatch.StartNew();
for (var i = 0; i < repetitions; i++)
{
SetPropertyWithDynamic(foo);
}
watch.Stop();
Console.WriteLine($"Dynamic benchmark: {watch.ElapsedTicks}");
watch = Stopwatch.StartNew();
for (var i = 0; i < repetitions; i++)
{
SetPropertyWithReflection(foo);
}
watch.Stop();
Console.WriteLine($"Reflection benchmark: {watch.ElapsedTicks}");
}
Console.WriteLine(foo);
Console.ReadLine();
}
static void SetPropertyWithDynamic(object o, bool isWarmup = false)
{
if (isWarmup)
return;
((dynamic)o).TheProperty = 1;
}
static void SetPropertyWithReflection(object o)
{
o.GetType().GetProperty("TheProperty").SetValue(o, 1);
}
public class Foo
{
public int TheProperty { get; set; }
public override string ToString() => $"Foo: {TheProperty}";
}
找出第一次运行和后续运行之间的区别吗?
关于c# - 当属性已知时,为什么不使用 `dynamic` 而不是反射?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48109071/