Attribute作为一种标记在我们的.net中随处可见,比如DatContract,DatMember,Serializable等等,各种用途的标记。是的我们的代码更加简洁,对于Attribute用好了,可以很好的简化我们的开发,比如PostSharp的AOP实现就是一种基于Attribute的标记编译时注入。在随笔中有关于IOC,AOP利用Attribute标记简化开发的实例。

   在使用Attribute时候发现了些鲜为人知的特性:

1:利用GetCustomAttributes传入的Attribute返回得到包括派生类。

2:GetCustomAttributes每次返回的对象都是经过发射出来的没有缓存。

   1:GetCustomAttributes传入的Attribute返回得到包括派生类:

       这里将采用一个测试类来验证:

[AttributeUsage(AttributeTargets.Class)] 
  public class TestImplementsAttribute : Attribute 
  { 
      public string Name 
      { get; set; } 
  }

 

private static void TestMutilpeImplements() 

    var type = typeof(Program); 
    var attrs = type.GetCustomAttributes(typeof(TestImplementsAttribute), false); 
    Console.WriteLine(string.Format("TestImplementsAttribute:({0})",attrs.Length)); 
    foreach (var item in attrs) 
    { 
        Console.WriteLine("  " + item.GetType().FullName); 
    } 
    attrs = type.GetCustomAttributes(typeof(SerializableAttribute), false); 
    Console.WriteLine(string.Format("SerializableAttribute:({0})", attrs.Length)); 
    foreach (var item in attrs) 
    { 
        Console.WriteLine("  " + item.GetType().FullName); 
    } 

    attrs = type.GetCustomAttributes(typeof(Attribute), false); 
    Console.WriteLine(string.Format("(base type)Attribute:({0})", attrs.Length)); 
    foreach (var item in attrs) 
    { 
        Console.WriteLine("  " + item.GetType().FullName); 
    } 

}

输出为: 

Attribute鲜为人知的两个特性记录_Attribute

这里我们可以很清晰的看见当传入Attribute类型时候返回包含了SerializableAttribute和TestImplementsAttribute两个。

2:GetCustomAttributes每次返回的对象都是经过发射出来的没有缓存:

测试代码可以看出来,不是同一个地址引用:

private static void TestAttributeActiver() 
      { 
          var type = typeof(Program); 
          var attr1 = type.GetCustomAttributes(typeof(TestImplementsAttribute), false)[0]; 
          var attr2 = type.GetCustomAttributes(typeof(TestImplementsAttribute), false)[0]; 
          Console.WriteLine(Object.ReferenceEquals(attr1, attr2));            
      }

输出值为false。 

我们在看看

.下面是 reflector的反编译结果(Attribute.GetCustomAttributes):

internal static unsafe object[] GetCustomAttributes(Module decoratedModule, int decoratedMetadataToken, int pcaCount, RuntimeType attributeFilterType, bool mustBeInheritable, IList derivedAttributes)
{
    if (decoratedModule.Assembly.ReflectionOnly)
    {
        throw new InvalidOperationException(Environment.GetResourceString("Arg_ReflectionOnlyCA"));
    }
    MetadataImport metadataImport = decoratedModule.MetadataImport;
    CustomAttributeRecord[] customAttributeRecords = CustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedMetadataToken);
    Type elementType = (((attributeFilterType == null) || attributeFilterType.IsValueType) || attributeFilterType.ContainsGenericParameters) ? typeof(object) : attributeFilterType;
    if ((attributeFilterType == null) && (customAttributeRecords.Length == 0))
    {
        return (Array.CreateInstance(elementType, 0) as object[]);
    }
    object[] attributes = Array.CreateInstance(elementType, customAttributeRecords.Length) as object[];
    int length = 0;
    SecurityContextFrame frame = new SecurityContextFrame();
    frame.Push(decoratedModule.Assembly.InternalAssembly);
    Assembly lastAptcaOkAssembly = null;
    for (int i = 0; i < customAttributeRecords.Length; i++)
    {
        bool flag2;
        bool flag3;
        object obj2 = null;
        CustomAttributeRecord caRecord = customAttributeRecords[i];
        RuntimeMethodHandle ctor = new RuntimeMethodHandle();
        RuntimeType attributeType = null;
        int namedArgs = 0;
        IntPtr signature = caRecord.blob.Signature;
        IntPtr blobEnd = (IntPtr) (((void*) signature) + caRecord.blob.Length);
        if (FilterCustomAttributeRecord(caRecord, metadataImport, ref lastAptcaOkAssembly, decoratedModule, decoratedMetadataToken, attributeFilterType, mustBeInheritable, attributes, derivedAttributes, out attributeType, out ctor, out flag2, out flag3))
        {
            if (!ctor.IsNullHandle())
            {
                ctor.CheckLinktimeDemands(decoratedModule, decoratedMetadataToken);
            }
            RuntimeConstructorInfo.CheckCanCreateInstance(attributeType, flag3);
            if (flag2)
            {
                obj2 = CreateCaObject(decoratedModule, ctor, ref signature, blobEnd, out namedArgs);
            }
            else
            {
                obj2 = attributeType.TypeHandle.CreateCaInstance(ctor);
                if (Marshal.ReadInt16(signature) != 1)
                {
                    throw new CustomAttributeFormatException();
                }
                signature = (IntPtr) (((void*) signature) + 2);
                namedArgs = Marshal.ReadInt16(signature);
                signature = (IntPtr) (((void*) signature) + 2);
            }
            for (int j = 0; j < namedArgs; j++)
            {
                string str;
                bool flag4;
                Type type3;
                object obj3;
                IntPtr ptr1 = caRecord.blob.Signature;
                GetPropertyOrFieldData(decoratedModule, ref signature, blobEnd, out str, out flag4, out type3, out obj3);
                try
                {
                    if (flag4)
                    {
                        if ((type3 == null) && (obj3 != null))
                        {
                            type3 = (obj3.GetType() == typeof(RuntimeType)) ? typeof(Type) : obj3.GetType();
                        }
                        RuntimePropertyInfo property = null;
                        if (type3 == null)
                        {
                            property = attributeType.GetProperty(str) as RuntimePropertyInfo;
                        }
                        else
                        {
                            property = attributeType.GetProperty(str, type3, Type.EmptyTypes) as RuntimePropertyInfo;
                        }
                        RuntimeMethodInfo setMethod = property.GetSetMethod(true) as RuntimeMethodInfo;
                        if (setMethod.IsPublic)
                        {
                            setMethod.MethodHandle.CheckLinktimeDemands(decoratedModule, decoratedMetadataToken);
                            setMethod.Invoke(obj2, BindingFlags.Default, null, new object[] { obj3 }, null, true);
                        }
                    }
                    else
                    {
                        (attributeType.GetField(str) as RtFieldInfo).InternalSetValue(obj2, obj3, BindingFlags.Default, Type.DefaultBinder, null, false);
                    }
                }
                catch (Exception exception)
                {
                    throw new CustomAttributeFormatException(string.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString(flag4 ? "RFLCT.InvalidPropFail" : "RFLCT.InvalidFieldFail"), new object[] { str }), exception);
                }
            }
            if (!signature.Equals(blobEnd))
            {
                throw new CustomAttributeFormatException();
            }
            attributes[length++] = obj2;
        }
    }
    frame.Pop();
    if ((length == customAttributeRecords.Length) && (pcaCount == 0))
    {
        return attributes;
    }
    if (length == 0)
    {
        Array.CreateInstance(elementType, 0);
    }
    object[] destinationArray = Array.CreateInstance(elementType, (int) (length + pcaCount)) as object[];
    Array.Copy(attributes, 0, destinationArray, 0, length);
    return destinationArray;
}

在这里我们可以见数组的创建CreateInstance等等。

   同时可以参见老赵前辈以前的关于Attribute反射的一次失败的尝试(上):原来GetCustomAttributes方法每次都返回新的实例一次失败的尝试(下):无法使用泛型的Attribute

   不知道为什么在Attribute参数的检查是在我们的编译时期,参数必须是常量表达式,却在这里需要每次反射。

   本篇随笔只是个人使用心得记录,请勿拍砖。