C# 特性
Posted CoderPro
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 特性相关的知识,希望对你有一定的参考价值。
1. 特性的作用
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。将特性与程序实体相关联后,可以在运行时使用反射查询特性。
2. 特性具有的属性
-
特性向程序添加元数据。元数据是程序中定义的类型的相关信息。所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。可以添加自定义特性来指定所需的其他任何信息。 -
可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性) -
特性可以像方法和属性一样接受自变量 -
程序可使用反射来检查自己的元数据或其他程序中的元数据
3. 特性使用
在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。如下所示:
// Serializable指定当前类为可序列化对象
[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
// 带参数形式,表示C#中使用到了非托管代码的方法
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();
特性类的类名约定以"Attribute"结尾,如DllImport特性的类名为"DllImportAttribute",但在使用时不同写"Attribute"
4. 特性目标
项目 | 说明 |
---|---|
assembly | 整个程序集 |
module | module 当前程序集模块 |
field | 类或结构中的字段 |
event | 事件 |
method | 方法或get和set属性访问器 |
param | 方法参数或set属性访问器参数 |
property | 属性 |
return | 方法、属性索引器或get属性访问器的返回值 |
type | 结构、类、接口、枚举或委托 |
更多模板类型参见System.AttributeTargets
5. 自定义特性
自定义特性需要继承Attribute,代码如下:
namespace CustomAttribute
{
// 指定MyAttribute可以被哪些目标使用
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
class MyAttribute : Attribute
{
private string name;
public double version;
public MyAttribute(string name)
{
this.name = name;
version = 1.0;
}
}
}
namespace CustomAttribute
{
// 使用自定义特性
[My("my",version = 1.1)]
class UseMyAttribute
{
}
}
6. 特性常见用途
-
在 Web 服务中使用 WebMethod 特性标记方法,以指明方法应可通过 SOAP 协议进行调用。有关详细信息,请参阅 WebMethodAttribute -
描述在与本机代码互操作时如何封送方法参数。有关详细信息,请参阅 MarshalAsAttribute -
描述类、方法和接口的 COM 属性 -
使用 DllImportAttribute 类调用非托管代码 -
从标题、版本、说明或商标方面描述程序集 -
描述要序列化并暂留类的哪些成员 -
描述如何为了执行 XML 序列化在类成员和 XML 节点之间进行映射 -
描述的方法的安全要求 -
指定用于强制实施安全规范的特征 -
通过实时 (JIT) 编译器控制优化,这样代码就一直都易于调试 -
获取方法调用方的相关信息
7. 开发中常用 - 例子(根据个人经验)
-
使用反射访问特性代码示例如下:
// 核心代码如下,此例子基于自定义属性的理解完成
private static void PrintCustomAttributeInfo(Type t)
{
Console.WriteLine($"MyAttribute 信息 {t}");
// 使用反射获取自定以特性
Attribute[] attrs = Attribute.GetCustomAttributes(t);
// 输出自定义属性内容
foreach (Attribute attr in attrs)
{
if (attr is MyAttribute)
{
MyAttribute a = attr as MyAttribute;
Console.WriteLine(" name: {0}, version {1:f}", a.GetVersionName(), a.version);
}
}
} -
MarshalAsAttribute : MarshalAsAttribute属性指示如何在托管代码和非托管代码之间封送数据,代码示例参见DllImportAttribute调用非托管代码
-
使用DllImportAttribute 类调用非托管代码
-
创建非托管代码库,代码如下:
HMODULE g_hModule;
BOOL APIENTRY DllMain( HMODULE hModule, // 本模块句柄
DWORD ul_reason_for_call, // 调用原因
LPVOID lpReserved // 隐式加载和显式加载
)
{
switch (ul_reason_for_call)
{
// 进程映射
// 个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接和动态链接的LoadLibrary或者LoadLibraryEx。
// 当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。
// 如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
// 不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
case DLL_PROCESS_ATTACH:
g_hModule = hModule;
break;
// 线程映射
// 当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数
// 新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数
// 注意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。
case DLL_THREAD_ATTACH:
break;
// 线程卸载
// 如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
// 注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。
case DLL_THREAD_DETACH:
break;
// 进程卸载
// 当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。
// DLL被从进程的地址空间解除映射的两种情况:
// 1. FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)
// 2. 进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)
// 注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
//自定义导出函数
__declspec(dllexport) void ExportFunc(LPCSTR pszContent)
{
char sz[MAX_PATH];
::GetModuleFileNameA(g_hModule, sz, MAX_PATH);
::MessageBoxA(NULL, pszContent, strrchr(sz, '\\') + 1, MB_OK);
} -
C#中使用非托管代码,代码如下:
[DllImport(@"..\..\..\Debug\CSharpCallbackTest.dll",CallingConvention = CallingConvention.Cdecl, EntryPoint = "ExportFunc")]
public static extern void CallTest([MarshalAs(UnmanagedType.LPStr)] string content);
static void Main(string[] args)
{
CallTest("测试");
Console.ReadKey();
} -
调用方信息: 使用信息属性,可以获取有关方法调用方的信息。可以获取源代码的文件路径、源代码中的行号和调用方的成员名称。
// 此示例代码来自微软官方文档
// CallerFilePathAttribute : 包含调用方的源文件的完整路径。 完整路径是编译时的路径
// CallerLineNumberAttribute : 源文件中调用方法的行号
// CallerMemberNameAttribute : 调用方的方法名称或属性名称
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31 -
以上是关于C# 特性的主要内容,如果未能解决你的问题,请参考以下文章
此 Canon SDK C++ 代码片段的等效 C# 代码是啥?