【问题标题】:Can C# Attributes access the Target Class?C# 属性可以访问目标类吗?
【发布时间】:2010-02-21 23:57:06
【问题描述】:

我想使用反射从属性类中访问类的属性。有可能吗?

例如:

class MyAttribute : Attribute
{
    private void AccessTargetClass()
    {
        // Do some operations
    }
}

[MyAttribute]
class TargetClass
{
}

【问题讨论】:

    标签: c# reflection attributes


    【解决方案1】:

    在某些情况下,您不能依赖调用代码来传递类型引用。

    以下是我为解决此问题而设计的解决方案。这有点像霰弹枪的方法,但它确实有效......

    using System;
    using System.Reflection;
    using System.Collections.Generic;
    using System.Linq;
    using System.Diagnostics;
    
    
    namespace Ethica.Reflection
    {
        /// <summary>
        /// Helps in discovery of the target of an Attribute
        /// </summary>
        /// <typeparam name="TAttribute">An Attribute derived type</typeparam>
        /// <remarks>
        /// The .NET framework does not provide navigation from attributes back to their targets, principally for the reason that 
        /// in typical usage scenarios for attributes, the attribute is discovered by a routine which already has a reference to a 
        /// member type.
        /// 
        /// There are, however, bona-fide cases where an attribute needs to detect it's target - an example is a localizable sub-class of the 
        /// DescriptionAttribute. In order for the DescriptionAttribute to return a localized string, it requires a resource key and, ideally,
        /// a type reference as the base-key for the ResourceManager. A DescriptionAttribute could not provide this information without
        /// a reference to it's target type.
        /// 
        /// Note to callers:
        /// 
        /// Your Attribute-derived class must implement Equals and GetHashCode, otherwise a run-time exception will occur, since this class
        /// creates a dictionary of attributes in order to speed up target lookups.
        /// </remarks>
        public static class AttributeTargetHelper<TAttribute>
            where TAttribute : Attribute
        {
            /// <summary>
            /// Map of attributes and their respective targets
            /// </summary>
            private static Dictionary<TAttribute, object> targetMap;
    
            /// <summary>
            /// List of assemblies that should not be rescanned for types.
            /// </summary>
            private static List<string> skipAssemblies;
    
            /// <summary>
            /// Adds an attribute and it's target to the dictionary
            /// </summary>
            /// <param name="attribute"></param>
            /// <param name="item"></param>
            private static void Add(TAttribute attribute, object item)
            {
                targetMap.Add(attribute, item);
            }
    
            /// <summary>
            /// Scans an assembly for all instances of the attribute.
            /// </summary>
            /// <param name="assembly"></param>
            private static void ScanAssembly(Assembly assembly)
            {
                const BindingFlags memberInfoBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
    
                if (!skipAssemblies.Contains(assembly.FullName))
                {
                    skipAssemblies.Add(assembly.FullName);
    
                    Debug.WriteLine("Loading attribute targets for " + typeof(TAttribute).Name + " from assembly " + assembly.FullName);
    
                    foreach (TAttribute attr in assembly.GetCustomAttributes(typeof(TAttribute), false))
                        Add(attr, assembly);
    
                    foreach (Type type in assembly.GetTypes())
                    {
                        foreach (TAttribute attr in type.GetCustomAttributes(typeof(TAttribute), false))
                            Add(attr, type);
    
                        foreach (MemberInfo member in type.GetMembers(memberInfoBinding))
                        {
                            foreach (TAttribute attr in member.GetCustomAttributes(typeof(TAttribute), false))
                                Add(attr, member);
    
                            if (member.MemberType == MemberTypes.Method)
                                foreach (var parameter in ((MethodInfo)member).GetParameters())
                                    foreach (TAttribute attr in parameter.GetCustomAttributes(typeof(TAttribute), false))
                                        Add(attr, parameter);
                        }
                    }
                }
    
                foreach (var assemblyName in assembly.GetReferencedAssemblies())
                {
                    if (!skipAssemblies.Contains(assemblyName.FullName))
                        ScanAssembly(Assembly.Load(assemblyName));
                }
            }
    
            /// <summary>
            /// Returns the target of an attribute.
            /// </summary>
            /// <param name="attribute">The attribute for which a target is sought</param>
            /// <returns>The target of the attribute - either an Assembly, Type or MemberInfo instance.</returns>
            public static object GetTarget(TAttribute attribute)
            {
                object result;
                if (!targetMap.TryGetValue(attribute, out result))
                {
                    // Since types can be loaded at any time, recheck that all assemblies are included...
                    // Walk up the stack in a last-ditch effort to find instances of the attribute.
                    StackTrace stackTrace = new StackTrace();           // get call stack
                    StackFrame[] stackFrames = stackTrace.GetFrames();  // get method calls (frames)
    
                    // write call stack method names
                    foreach (StackFrame stackFrame in stackFrames)
                    {
                        Console.WriteLine(stackFrame.GetMethod().Name);   // write method name
                        ScanAssembly(stackFrame.GetMethod().GetType().Assembly);
                    }
    
                    if (!targetMap.TryGetValue(attribute, out result))
                        throw new InvalidProgramException("Cannot find assembly referencing attribute");
                }
                return result;
            }
    
            /// <summary>
            /// Static constructor for type.
            /// </summary>
            static AttributeTargetHelper()
            {
                targetMap = new Dictionary<TAttribute, object>();
    
                // Do not load any assemblies reference by the assembly which declares the attribute, since they cannot possibly use the attribute
                skipAssemblies = new List<string>(typeof(TAttribute).Assembly.GetReferencedAssemblies().Select(c => c.FullName));
    
                // Skip common system assemblies
                skipAssemblies.Add("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
                skipAssemblies.Add("System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                skipAssemblies.Add("System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
                skipAssemblies.Add("System.Data.SqlXml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
                skipAssemblies.Add("System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                skipAssemblies.Add("System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
    
                // Scan the entire application
                ScanAssembly(Assembly.GetEntryAssembly());
            }
    
        }
    
    
        /// <summary>
        /// Extends attributes so that their targets can be discovered
        /// </summary>
        public static class AttributeTargetHelperExtension
        {
            /// <summary>
            /// Gets the target of an attribute
            /// </summary>
            /// <typeparam name="TAttribute"></typeparam>
            /// <param name="attribute">The attribute for which a target is sought</param>
            /// <returns>The target of the attribute - either an Assembly, Type or MemberInfo instance.</returns>
            public static object GetTarget<TAttribute>(this TAttribute attribute)
                where TAttribute : Attribute
            {
                return AttributeTargetHelper<TAttribute>.GetTarget(attribute);
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      不是直接的,但是为了让属性做任何事情,你必须有一个类型/成员的句柄并调用它的 GetCustomAttributes 方法,所以你可以简单地从那里传递它:

      class MyAttribute : Attribute
      {
          public void DoSomethingWithDeclaringType(Type t)
          {
          }
      }
      

      像这样使用它:

      Type classType = typeof(MyClass);
      object[] attribs = classType.GetCustomAttributes(typeof(MyAttribute), true);
      if(attribs.Length > 0)
      {
          ((MyAttribute)attribs[0]).DoSomethingWithDeclaringType(classType);
      }
      

      【讨论】:

      • 您还可以在属性构造函数中提供声明类型,让您可以访问该类型。例如。 ` class MyAttribute : Attribute { private void MyAttribute( Type declaringType ) { // 在此处访问声明类型,甚至在需要时保留引用。 } } [MyAttribute( typeof(TargetClass) )] 类 TargetClass { } `
      【解决方案3】:

      Attributes 主要目的是注解:你声明?该类/方法/属性应该以这种或那种方式处理,但实际分析所有这些东西的代码应该在其他地方。所以你打算在属性内部做一些事情正在打破这种模式。我的建议:考虑另一种解决方案。

      【讨论】:

      • 等到“DoSomethingWithDeclaringType”工作的方法,但这意味着以某种方式强制检查仅在第一次调用 DoSomethingWithDeclaringType 时完成,这意味着确保没有人直接查找属性并对其进行处理而无需通过审批功能。如果可以的话,我宁愿将此验证代码放入属性本身,而且我认为如果可以的话,这不会违反模式。
      • 假设一个属性正在建立一个类和另一个类之间的关系。您将验证代码放在哪里,上面写着“是的,这两个类实际上是相互兼容的?”我只想在加载时运行该代码一次,当属性建立时,我不想依赖每个添加属性的类来确保他们将代码放入以验证连接。等待“DoSomethingWithDeclaringType”的方法有效,但这意味着以某种方式强制检查仅在第一次调用 DoSomethingWithDeclaringTyp 时完成
      【解决方案4】:

      您可以使用 .NET 4.5 中的CallerMemberNameAttribute 访问调用者类型:

      public CustomAttribute([CallerMemberName] string propertyName = null)
      {
          // ...
      }
      

      然后使用简单的反射

      【讨论】:

        猜你喜欢
        • 2021-01-10
        • 1970-01-01
        • 2013-06-22
        • 2015-05-31
        • 1970-01-01
        • 2012-01-03
        • 2012-10-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多