从动态生成的类中获取属性

6

我遇到了一个小问题,通常的解决方案似乎不能解决它。

请考虑:

我有一组动态生成的类,继承自一个已知的基类(我们称之为BaseClass)。 这些动态生成的类还具有动态生成的属性及其关联属性。

这些属性也是一个自定义类,但不是动态生成的:

[AttributeUsage(AttributeTargets.Property)]
class TypeAttribute: Attribute
{
    private Type _type;
    public Type Type
    {
        get { return _type; }
    }

    public TypeAttribute(Type t)
    {
        _type = t;
    }
}

然后我想在运行时获取已分配属性的值:
List<PropertyInfo> result = target.GetType()
  .GetProperties()
  .Where(
     p =>
        p.GetCustomAttributes(typeof(TypeAttribute), true)
        //.Where(ca => ((TypeAttribute)ca).)
        .Any()
     )
  .ToList();

其中targetBaseClass的子类。但是,列表result为空,这让我感到困惑。

我使用以下方式添加属性:

PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, 
           PropertyAttributes.HasDefault, propertyType, null);
ConstructorInfo classCtorInfo = typeof(TypeAttribute).
           GetConstructor(new Type[] { typeof(Type) });
CustomAttributeBuilder myCABuilder = new CustomAttributeBuilder(
           classCtorInfo, new object[] { getType(dataType) });
propertyBuilder.SetCustomAttribute(myCABuilder);

其中dataType是要存储在属性中的类型,tb是类的TypeBuilder

如果我在属性上执行getCustomAttributes(),我会得到期望的属性,除了我要找的那个。但是如果我执行getCustomAttributesData(),我会得到所有属性,但我要找的那个是CustomAttributeData类型的,无法转换为TypeAttribute(如果我在VS调试器中检查实例,我可以看到包含信息是TypeAttribute)。 我猜这是问题的症状,但我找不到原因,更不用说解决方法了。

有人能告诉我为什么result列表为空吗?


哦,这很有趣;我需要几分钟来设置一个测试环境……我希望在我这样做的时候有人能发现一些明显的问题! - Marc Gravell
1个回答

2

对我来说运行良好;您确定您的属性和getter或setter方法足够存在,以便它实际显示为属性吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
[AttributeUsage(AttributeTargets.Property)]
public class TypeAttribute : Attribute
{
    private Type _type;
    public Type Type
    {
        get { return _type; }
    }

    public TypeAttribute(Type t)
    {
        _type = t;
    }
}

public class BaseClass
{

}

static class Program
{
    static void Main()
    {
        var aName = new AssemblyName("MyAssembly");
        var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
                aName, AssemblyBuilderAccess.RunAndSave);
        var mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
        var tb = mb.DefineType("MyType", TypeAttributes.Public, typeof(BaseClass));

        var propertyName = "MyProperty";
        var propertyType = typeof(int);
        var propertyBuilder = tb.DefineProperty(propertyName,
           PropertyAttributes.HasDefault, propertyType, null);
        var classCtorInfo = typeof(TypeAttribute).
                   GetConstructor(new Type[] { typeof(Type) });

        Type tArg = typeof(float); // for no real reason
        var myCABuilder = new CustomAttributeBuilder(
            classCtorInfo, new object[] { tArg });
        propertyBuilder.SetCustomAttribute(myCABuilder);

        var field = tb.DefineField("myField", propertyType, FieldAttributes.Private);
        var getter = tb.DefineMethod("get_" + propertyName,
            MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public,
            propertyType, Type.EmptyTypes);
        propertyBuilder.SetGetMethod(getter);
        var il = getter.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, field);
        il.Emit(OpCodes.Ret);
        var setter = tb.DefineMethod("set_" + propertyName,
            MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public,
            typeof(void), new[] { typeof(int) });
        il = setter.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, field);
        il.Emit(OpCodes.Ret);
        propertyBuilder.SetSetMethod(setter);
        var target = Activator.CreateInstance(tb.CreateType());        

        List<PropertyInfo> result = target.GetType()
          .GetProperties()
          .Where(
             p =>
                p.GetCustomAttributes(typeof(TypeAttribute), true)
                 //.Where(ca => ((TypeAttribute)ca).)
                .Any()
             ).ToList();
    }
}

此外,如果您只关心它是否存在,使用 Attribute.IsDefined 更加高效。
List<PropertyInfo> result = target.GetType().GetProperties()
      .Where(p => Attribute.IsDefined(p, typeof(TypeAttribute), true)).ToList();

请注意,我必须伪造getType(dataType)的东西 - 因为它不在问题中,我无法看到它正在做什么。请确保它返回一个Type

那个方法按照你说的确实有效。我感觉有点傻,之前没有尝试在单独的问题中运行它,但即使我这样做了,我仍然会在这里,只是问的问题不同了。但是,当我将这个可行的代码复制到我的“真正”的应用程序中时,它就无法工作了。结果是一个长度为零的列表。同样的代码复制到WPF应用程序中。为了排除WPF是罪魁祸首,我还创建了一个空的WPF应用程序,它可以工作。所以问题是找出是什么导致了我的“真正应用程序”出现问题... - fredrik
1
这是一个非常愚蠢的错误。显然,忘记将 TypeAttribute 声明为公共的会导致这种情况发生。如果没有你的可工作代码,我可能不会注意到它。谢谢。 - fredrik
当我尝试执行result[0].GetCustomAttributes()时,自动生成的程序集会出现FileNotFoundException。你有什么想法我需要做什么吗?(当然,我检查了列表中至少有一个结果) - fredrik
@fredrik 如果没有可重现的示例,我是不会这么做的;我的第一个想法是 RunAndSave,但除此之外:我需要看到它发生的情况。 - Marc Gravell
我刚刚成功解决了它。我必须显式调用 assemblyBuilder.Save(),然后一切都正常了。我可以接受这个结果。感谢您的帮助。 - fredrik
@fredrik “显然,忘记将TypeAttribute声明为public会导致这个问题。”哇,我把属性声明为internal,纠结了几个小时,然后看到了你的评论。 - Pierre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接