1.特性功能:
用以将元数据或声明信息与代码(程序集、类型、方法、属性等)相关联。特性与程序实体相关联后,即可在运行时用反射技术查询特性。
2.使用特性:
特性可以放置在几乎所有的声明中(但特定的特性可能限制在其上有效的声明类型)。
2.1.普通特性:
[System.Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
2.2.具有 DllImportAttribute 特性的方法的声明如下:
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
extern static void SampleMethod();
2.3.一个声明上可放置多个特性:
using System.Runtime.InteropServices;
static void MethodA([In][Out] ref double x)
{
}
static void MethodB([Out][In] double x)
{
}
static void MethodC([Out, In]double x)
{
}
2.4.某些特性对于给定实体可以指定多次,如, ConditionalAttribute就是一个可以多次使用的特性:
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}
3.特性参数:
定位参数(特性类的构造函数的参数)、属性参数(特性类中的属性),也叫命名参数
定位参数是必填的且必须按顺序指定, 属性参数是可选的且可以按任意顺序指定。定位参数必须在前。
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
第一个参数(DLL 名称)是定位参数并且总是第一个出现,其他参数为命名参数。
在这种情况下,两个命名参数均默认为 false,因此可将其省略。
有关默认参数值的信息,请参考各个特性的文档。
4.特性目标:
特性的目标是应用该特性的实体,特性可以应用于类、特定方法或整个程序集。默认情况下,特性应用于它后面的元素。但是,您也可以显式标识要将特性应用于类,方法还是它的参数或返回值。
若要显式标识特性目标,请使用下面的语法来应用特性到目标上:
[target : attribute-list]
特性的目标列表:
C# | Visual Basic | 适用对象 |
---|---|---|
assembly | Assembly | 整个程序集 |
module | Module | 当前程序集模块(不同于 Visual Basic 模块) |
field | 不支持 | 在类或结构中的字段 |
event | 不支持 | event |
method | 不支持 | 方法或 get 和 set 属性访问器 |
param | 不支持 | 方法参数或 set 属性访问器参数 |
property | 不支持 | 属性 |
return | 不支持 | 方法、属性索引器或 get 属性访问器的返回值 |
type | 不支持 | 结构、类、接口、枚举或委托 |
将特性应用于程序集和模块:
[assembly:AssemblyTitle("Production assembly4")]
[module:CLSCompliant(true)]
将特性应用于方法、方法参数和方法返回值:
[method:MyAttr]
static int Method()
{
return 1;
}
或
[MyAttr]
static int Method()
{
return 1;
}
// applies to return value
[return: SomeAttr]
int Method3() { return 0; }
[property:SomeAttr]
public int Age{get;set;};
或
[SomeAttr]
public int Age{get;set;};
无论规定 SomeAttr 应用于什么目标,都必须指定 return 目标,即使 SomeAttr 被定义为仅应用于返回值也是如此。换言之,编译器将不使用 AttributeUsage 信息解析不明确的特性目标。
5.特性的用途:
5.1.在Web服务中, 使用WebMethod特性来标记方法, 以指示该方法可以通过SOAP协议进行调用,
5.2.描述当与本机代码进行交互操作时如何封送方法参数(MarshalAsAttribute)
5.3.描述类、方法和接口的 COM 属性。
5.4.使用 DllImportAttribute 类调用非托管代码。
5.5.在标题、版本、说明或商标方面描述您的程序集。
5.6.描述要持久性序列化类的哪些成员。
5.7.描述如何映射类成员和 XML 节点以便进行 XML 序列化。
5.8.描述方法的安全要求。
5.9.指定用于强制安全性的特性。
5.11.由实时 (JIT) 编译器控制优化,以便易于调试代码。
5.12.获取有关调用方的信息的方法。
6.C#常用特性:
6.1.全局特性:
用于整个程序集或模块:如:AssemblyVersionAttribute用于向程序集中嵌入版本信息:
[assembly: AssemblyVersion("1.0.0.0")]
6.2.过时特性:
Obsolete 属性指示某个程序实体标记为建议不再使用的一个。 每次使用Obsolete标记过的实体会出现警告或错误提示。
[System.Obsolete("use NewMethod", true)]
public void OldMethod() { }
6.3.条件特性:Conditional
利用 Conditional 属性,程序员可以定义条件方法。Conditional 属性通过测试条件编译符号来确定适用的条件。当运行到一个条件方法调用时,是否执行该调用,要根据出现该调用时是否已定义了此符号来确定。如果定义了此符号,则执行该调用;否则省略该调用(包括对调用的参数的计算):
<strong>#define TRACE_ON</strong>
using System;
using System.Diagnostics;
public class Trace
{
[Conditional("TRACE_ON")]
public static void Msg(string msg)
{
Console.WriteLine(msg);
}
}
public class ProgramClass
{
static void Main()
{
Trace.Msg("Now in Main...");
Console.WriteLine("Done.");
}<pre name="code" class="csharp">#if DEBUG
void ConditionalMethod()
{
}
#endif
} 如果没#define TRANCE_ON这一指令,则在main函数中不执行Msg方法,直接输出Done.Conditional 一般用于在程序Debug版本中使用DEBUG标识符来启用跟踪并记录功能:
[Conditional("DEBUG")]//DEBUG指令是系统指令,不用再用#define DEBUG定义
static void DebugMethod()
{
}
此时,在Debug版本中会调用DebugMethod,在Release版本中会跳过对他的调用。效果和#if DEBUG...#endif一样
如果一个方法有多个Condigional特性,则只要有一个条件符号被定义了,则该方法就会被调用。(相当于用or连接多个特性)
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}//A,B中只要有一个被定义了, 就会调用。
Condigional也可用于特性本身:
[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
string text;
public DocumentationAttribute(string text)
{
this.text = text;
}
}
class SampleClass
{
// This attribute will only be included if DEBUG is defined.
[Documentation("This method displays an integer.")]//只有定义了DEBUG命令时,Dowork才能应用Documentation特性。
static void DoWork(int i)
{
System.Console.WriteLine(i.ToString());
}
}
6.4.调用方信息特性:
使用调用方信息特性, 可以获取调用方的信息, 并将它传递给方法。可以获取源代码文件路径,行号和调用方的成员名称:
CallerFilePathAttribute:包含调用方源文件在编译时的完整路径。
CallerLineNumberAttribute:在调用方法的源文件中的行号。
CallerMemberNameAttribute:方法名称或调用方的属性名称。
static void Main(string[] args)
{
TraceMessage("Something happened.");
Console.ReadKey();
}
public static void TraceMessage(string message,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
System.Diagnostics.Trace.WriteLine("message: " + message);
System.Diagnostics.Trace.WriteLine("member name: " + memberName);
System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
}
输出:
message: Something happened.
member name: Main
source file path: d:\Myprojects\MyTestProjects\AttributeTest\Program.cs
source line number: 17
6.5.DllImportAttribute特性:using System.Runtime.InteropServices
http://blog.csdn.net/hbqhdlc/article/details/6843650
6.5.1.使用DllImport特性可以直接调用一些已经存在的功能(如windows中的一些功能, C++中已经编写好的一些方法),它的功能是提供从非托管DLL导出的函数进行调用所必须的信息。该特性只能应用于方法, 要求
最少要提供包含入口点的dll的名称。
(托管DLL:指完全由.NET 托管代码实现的DLL,完全依赖于.NET平台的CLR运行, 受.NET CLR管控, 支持内存自动回收,对.NET平台是安全的DLL,非托管DLL:指完全或部分不是用.NET代码实现,
不依赖于.NET平台即可运行, 如COM方式的DLL,不支持内存自动回收, 对.NET平台而言,也是非安全的。)
6.5.2.说明:
6.5.2.1.DllImport只能放置在方法声明上。
6.5.2.2.DllImport具有单个定位参数:指定包含被导入方法的dll名称的dllName参数。
6.5.2.3.DllImport具有五个命名参数:
a.CallingConvention参数指示入口点的调用约定,如果未指定CallingConvention,则使用默认值CallingConvention.Winapi.
b.CharSet参数指定用再入口点的字符集, 如果未指定, 则默认CharSet.Auto.
c.EntryPoint参数给出dll中入口点的名称, 如果未指定, 则默认使用方法本身的名称。
d.ExactSpelling参数指示EntryPoint是否必须与指示的入口点的拼写完全匹配,如果未指定, 则默认false.
e.PreserveSig参数指示方法的签名被保留还是被转换,当签名被转换时, 它被转换为一个具有HRESULT返回值和该返回值的一个名为retval的附加输出参数的签名,如果未指定,则true.
f.SetLastError参数指示方法是否保留Win32"上一错误",默认false.
6.5.2.4.DllImport修饰的方法必须具有extern修饰符。
6.5.3.举例:
使用DllImport特性导入Win32的MessageBox函数:
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
MessageBox(<span style="color:Blue;">new</span> IntPtr(0), <span style="color:#A31515;">"Hello World!"</span>, <span style="color:#A31515;">"Hello Dialog"</span>, 0);
在调用MessageBox时就会弹出提示框。
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);//调用Beep()API来发出声音
6.5.4.DllImport路径问题:
会按照顺序自动去寻找dll:exe所在目录->System32所在目录->环境变量目录。
所以只需要你把引用的DLL 拷贝到这三个目录下 就可以不用写路径了。
7.自定义特性:
通过定义一个特性类,可以创建您自己的自定义特性。该特性类直接或间接地从 Attribute 派生,有助于方便快捷地在元数据中标识特性定义。
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class Author : System.Attribute
{
<span style="color:Blue;">private</span> <span style="color:Blue;">string</span> name;
<span style="color:Blue;">public</span> <span style="color:Blue;">double</span> version;
<span style="color:Blue;">public</span> Author(<span style="color:Blue;">string</span> name)
{
<span style="color:Blue;">this</span>.name = name;
version = 1.0;
}
}
如果特性类包含一个属性,则该属性必须为读写属性。
8.使用反射访问(检索)自定义特性:
如果没有检索自定义特性的信息和对其进行操作的方法,则定义自定义特性并将其放置在源代码中就没有意义。使用反射,可检索用自定义特性定义的信息。主要方法是 GetCustomAttributes,它返回对象数组,这些对象在运行时等效于源代码特性。
private static void PrintAuthorInfo(Type t)
{
Console.WriteLine("Author information for {0}",t);
Attribute[] attr = Attribute.GetCustomAttributes(t);
foreach (Attribute item in attr)
{
if (item is Author)
{
Author a = (Author)item;
Console.WriteLine( "{0},Version{1:f}",a.GetName(),a.version);
}
}
}
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class Author : System.Attribute
{
string name;
public double version;
public Author(string name)
{
this.name = name;
// Default value.
version = 1.0;
}
public string GetName()
{
return name;
}
}
// Class with the Author attribute.
[Author("P. Ackerman")]
public class FirstClass
{
// ...
}
PrintAuthorInfo(typeof(FirstClass));