【问题标题】:C# Strange behavior of CustomAttribute [duplicate]C# CustomAttribute 的奇怪行为
【发布时间】:2018-01-28 06:45:20
【问题描述】:

在我的应用程序中,我遇到了我无法理解的自定义属性和反射的奇怪情况,我试图减少问题。假设我有以下自定义属性:

class A : Attribute
{
    public virtual string SayHi()
    {
        return "Hi From A";
    }
}
class B : A
{
    public override string SayHi()
    {
        return "Hi From B";
    }
}

以下类用自定义属性装饰:

[A]
class X
{ }

[B]
class Y
{ }

在下面的方法中,我将用“A”属性修饰的每种类型的类映射到一个函数,该函数返回其自定义属性返回的值:

static Dictionary<Type, Func<string>> dic = new Dictionary<Type, Func<string>>();
static void func()
{
    A attr;
    foreach (var type in typeof(Program).Assembly.GetTypes())
    {
        var attrs = type.GetCustomAttributes(typeof(A)).ToList();
        if(attrs.Any())
        {
            attr = attrs.First() as A;
            dic.Add(type, () => attr.SayHi());
        }
    }
}

映射到 X 类型的函数可能会返回“Hi From A”,但奇怪的是,以下代码将“Hi From B”打印到控制台!

func();
Console.WriteLine(dic[typeof(X)]());

我是否缺少语言功能?

【问题讨论】:

  • “我错过了语言功能吗?” -- 你犯了经典的“多次捕获同一个变量”错误。见标记重复。您需要在循环中声明attr inside,以便添加到字典中的每个 lambda 都使用不同的变量。

标签: c# reflection custom-attributes


【解决方案1】:

此行为与属性无关。这是一个经典的“捕获变量”问题。您在 foreach 循环之外声明了 attr 变量,然后在委托中引用它,因此字典中的每个函数最终都会引用 attr 在运行后结束的最后一个值通过foreach

一个更简单的复制看起来像这样:

int x;
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    x = i;
    actions.Add(() => Console.WriteLine(x));
}
foreach (var action in actions)
{
    action();
}

输出:

2
2
2

如果您将声明移到循环中,您将获得您正在寻找的行为。

static void func()
{
    foreach (var type in typeof(Program).Assembly.GetTypes())
    {
        var attrs = type.GetCustomAttributes(typeof(A)).ToList();
        if(attrs.Any())
        {
            A attr = attrs.First() as A;
            dic.Add(type, () => attr.SayHi());
        }
    }
}

【讨论】:

  • 真正的 attr 不必要地在循环之外,但它分配了 'attr = attrs.First() as A;'在被添加到字典之前。
  • @Rob: "它分配了 'attr = attrs.First() as A;'" -- 是的,这正是问题所在。 lambda 是添加到字典中的内容,而不是变量的当前值。 lambda 捕获变量,因此每个 lambda 使用相同的变量,因此始终使用最新的值。
  • @RobSmyth:但 attr 没有被添加到字典中:Func&lt;string&gt; referringattr 被添加,然后 attr 被添加在随后的循环中重新分配。
  • 是的,你是对的。捕捉对我来说似乎很奇怪。顺便说一句,在循环之外声明变量不会影响代码的优化?
  • “不声明循环外的变量会影响代码的优化?” -- 影响,具体如何? C# 根本不做(很多)优化。 JIT 编译器可以很容易地为循环内部的变量重用变量槽,就像在外部一样。无论如何,如果您通常花时间在一种方法中移动变量以实现某种“优化”,那么您将注意力集中在错误的事情上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-03
  • 2019-04-10
  • 2019-05-24
  • 2016-07-15
  • 2011-06-23
  • 2016-08-12
  • 2013-10-28
相关资源
最近更新 更多