C# 自定义属性的奇怪行为

C# 自定义属性的奇怪行为,c#,reflection,custom-attributes,C#,Reflection,Custom Attributes,在我的应用程序中,我遇到了一个奇怪的情况,自定义属性和反射我无法理解,我试图减少这个问题。假设我有以下自定义属性: class A : Attribute { public virtual string SayHi() { return "Hi From A"; } } class B : A { public override string SayHi() { return "Hi From B"; } } [A

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

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
{ }
以下类用自定义属性修饰:

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
{ }
在以下方法中,我将使用属性修饰的每种类型的类映射到一个函数,该函数返回其自定义属性返回的值:

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());
        }
    }
}

是否缺少语言功能?

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

更简单的复制方式如下所示:

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();
}
如果您将声明移动到循环中,您将获得所需的行为

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());
        }
    }
}

True attr不必要地位于循环之外,但它将'attr=attrs.First赋值为;'在添加到字典之前。@Rob:它将'attr=attrs.First作为;'赋值是的,这正是问题所在。lambda是添加到字典中的内容,而不是变量的当前值。lambda捕获变量,因此每个lambda使用相同的变量,因此始终使用最新的值。@RobSmyth:但是没有将attr添加到字典中:正在添加一个引用attr的Func,然后在随后通过循环时重新分配attr。是的,您是对的。抓捕对我来说似乎很奇怪。顺便说一句,在循环之外声明变量不会影响代码的优化吗?在循环之外声明变量不会影响代码的优化吗究竟如何影响?C根本不做太多优化。JIT编译器可以轻松地为循环内部和外部的变量重用变量槽。在任何情况下,如果你通常花时间在方法中移动变量以实现某种优化,那么你的注意力就错了。我是否缺少语言功能您已使classic多次捕获同一变量。请参阅标记的副本。您需要在循环中声明attr,以便添加到字典中的每个lambda都使用不同的变量。