你可以定义自定义特性并将其放入源代码中这一事实,在没有检索该信息并对其进行操作的方法的情况下将没有任何价值。 通过使用反射,可以检索通过自定义特性定义的信息。 主要方法是 GetCustomAttributes,它返回对象数组,这些对象在运行时等效于源代码特性。 此方法有许多重载版本。
特性规范,例如:
[Author("P. Ackerman", Version = 1.1)]
class SampleClass { }
在概念上等效于以下代码:
var anonymousAuthorObject = new Author("P. Ackerman")
{Version = 1.1
};
但是,在为特性查询 SampleClass 之前,代码将不会执行。 对 SampleClass 调用 GetCustomAttributes 会导致构造并初始化一个 Author 对象。 如果该类具有其他特性,则将以类似方式构造其他特性对象。 然后 GetCustomAttributes 会以数组形式返回 Author 对象和任何其他特性对象。 之后你便可以循环访问此数组,根据每个数组元素的类型确定所应用的特性,并从特性对象中提取信息。
下面是完整的示例。 定义自定义特性、将其应用于多个实体,并通过反射对其进行检索。
// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |System.AttributeTargets.Struct,AllowMultiple = true) // Multiuse attribute.
]
public class AuthorAttribute : System.Attribute
{string Name;public double Version;public AuthorAttribute(string name){Name = name;// Default value.Version = 1.0;}public string GetName() => Name;
}// Class with the Author attribute.
[Author("P. Ackerman")]
public class FirstClass
{// ...
}// Class without the Author attribute.
public class SecondClass
{// ...
}// Class with multiple Author attributes.
[Author("P. Ackerman"), Author("R. Koch", Version = 2.0)]
public class ThirdClass
{// ...
}class TestAuthorAttribute
{public static void Test(){PrintAuthorInfo(typeof(FirstClass));PrintAuthorInfo(typeof(SecondClass));PrintAuthorInfo(typeof(ThirdClass));}private static void PrintAuthorInfo(System.Type t){System.Console.WriteLine($"Author information for {t}");// Using reflection.System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // Reflection.// Displaying output.foreach (System.Attribute attr in attrs){if (attr is AuthorAttribute a){System.Console.WriteLine($" {a.GetName()}, version {a.Version:f}");}}}
}
/* Output:Author information for FirstClassP. Ackerman, version 1.00Author information for SecondClassAuthor information for ThirdClassR. Koch, version 2.00P. Ackerman, version 1.00
*/
如何使用特性创建 C/C++ 联合
通过使用特性,可自定义结构在内存中的布局方式。 例如,可使用 StructLayout(LayoutKind.Explicit) 和 FieldOffset 特性在 C/C++ 中创建所谓的联合。
在此代码段中,TestUnion 的所有字段均从内存中的同一位置开始。
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{[System.Runtime.InteropServices.FieldOffset(0)]public int i;[System.Runtime.InteropServices.FieldOffset(0)]public double d;[System.Runtime.InteropServices.FieldOffset(0)]public char c;[System.Runtime.InteropServices.FieldOffset(0)]public byte b;
}
以下代码是另一个示例,其中的字段从不同的显式设置位置开始。
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{[System.Runtime.InteropServices.FieldOffset(0)]public long lg;[System.Runtime.InteropServices.FieldOffset(0)]public int i1;[System.Runtime.InteropServices.FieldOffset(4)]public int i2;[System.Runtime.InteropServices.FieldOffset(8)]public double d;[System.Runtime.InteropServices.FieldOffset(12)]public char c;[System.Runtime.InteropServices.FieldOffset(14)]public byte b;
}
组合的两个整数字段 i1 和 i2 与 lg 共享相同的内存位置。 lg 使用前 8 个字节,或 i1 使用前 4 个字节且 i2 使用后 4 个字节。 使用平台调用时,这种对结构布局的控制很有用。