前言
CSharp的特性(Attribute)是比较难以理解的技术,写代码时通常都要求写注释,为了是让其他程序猿快速理解代码含义,但是注释是写给'人'看的,突发奇想下:能不能写出给C#编译器看的注释,比如在某些代码段上打上标记,让编译器看到标记后,做出不同的运行效果?其实…这就是特性。
1.Serializable特性分析
为什么Serializable特性作为小节1讲解呢?因为它是比较常见的特性,在网络对象进行传输时和数据库进行对象保存时,使用序列化特性后的类、结构体、枚举等等都可实现序列化操作的,SerializableAttribute仅是标记而已,它并不执行序列化动作。这样为何在C#中必须要使用它呢?而其他语言好像没有C#这种技术,接下来展示序列化的代码:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace TestPro
{
[Serializable]
public class Person
{
public int Age { get; set; }
public int Sex { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Age = 18;
person.Sex = 0;
person.Name = "李洛克";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("PersonFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, person);
stream.Close();
}
}
}
运行生成.dll和.exe以后,通过ildasm查看il代码分析下原因,以下截图就是Person类的。
只是多了serializable指令,而且比起其他特性,它的构造函数都没有执行({}红色区域是正常特性的构造函数执行位置),从il代码看不出什么情况,只能从功能去猜想序列化实现的原理,通常来说序列化某个自定义类时,都要程序猿去实现具体的序列化方法(比如C、Delphi等等低级语言),而C#使用formatter.Serialize(stream, person)即可,这是什么神仙接口呢?(没有具体告诉接口类里有那些属性、变量),但使用它可完成基本的序列化动作,其实C#是通过反射这种技术去完成序列化接口的,了解反射原理的程序猿可自定义实现通用的序列化接口,反射打个比方就是文件夹下查找某个文件,使用序列化特性可以提高反射性能(查找对应类和获取到它的属性、变量性能),下面给出简单示意图。
使用序列化特性标记以后可提高性能,不用遍历程序集所有类、结构体,所以使用C#接口进行序列化时需要使用它,不然就自定义序列化接口(比如Person1使用Person1Serialize,Person2使用Person2Serialize)。如果对序列化方式不满意,但不想自定义序列化类,希望通过C#接口实现序列化也是有办法的,Person继承ISerializable,实现GetObjectData即可,比如序列化时给名字添加前缀或不序列化名字,代码实现如下。
[Serializable]
public class Person : ISerializable
{
public int Age { get; set; }
public int Sex { get; set; }
public string Name { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
info.AddValue("Sex", Sex);
info.AddValue("Name","火影忍者" + Name);
}
}
2.CSharp预定义特性
小节2在网上可以普遍查询到的资料,这里简单说明下情况,.Net框架提供三种预定义特性:
- AttributeUsage
- Conditional
- Obsolete
预定义特性 AttributeUsage 描述了如何使用自定义特性类。它规定了特性可应用到的项目的类型。规定该特性的语法如下:
[AttributeUsage(
validon,//AttributeTargets.Class...
AllowMultiple=allowmultiple,//该特性是否多用的
Inherited=inherited//该特性是否继承的
)]
Conditional预定义特性标记了条件方法,其方法是否被执行依赖指定的预处理标识符是否被定义。具体代码如下:
#define DEBUG
using System;
using System.Diagnostics;
public class Debug
{
[Conditional("DEBUG")]
public static void Log(string msg)
{
Console.WriteLine(msg);
}
[Conditional("DEBUG")]
public static void LogWarning(string msg)
{
Console.WriteLine("w:" + msg);
}
[Conditional("DEBUG")]
public static void LogError(string msg)
{
Console.WriteLine("e:" + msg);
}
}
class TestPro
{
public static void Main()
{
Debug.Log("In Main function.");
Console.ReadKey();
}
}
Obsolete预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。具体代码如下:
public class Math
{
[Obsolete("Don't use Cal, use gCal instead", true)]
public static void Cal()
{
}
public static void gCal()
{
}
}
3.(AttributeUsage)自定义特性使用
开发经常遇到的情况,就是结构体、类需要稍微调整下,但不想改动过大,而且改动逻辑是偏向行为方面的改变,比如消息协议类之前都是在单个服务器上运行的,经过实战以后发现服务器压力过大了,公司领导准备让程序猿优化服务器性能,首先想到的就是分布式服务器设计思路,把服务器功能分开后部署到不同服务器机器上(处理Http请求的服务器、数据库操作的服务器、网关服务器、处理游戏战斗模块服务器),这样可以很大程度减轻服务器的压力(把拆分出的服务器还是部署到一个机器上,就和之前没有区别,可能还会增加本地服务器之间微小的通信消耗),所以需要大量重构代码,这时可使用自定义特性的奇淫技巧来解决这方面问题,修改过的伪代码如下:
using System;
namespace TestPro
{
public enum AppType
{
Gate,
Realm,
Http,
DB
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class MessageHandlerAttribute : Attribute
{
public AppType appType { get; }
public Type selfType { get; }
public MessageHandlerAttribute()
{
}
public MessageHandlerAttribute(AppType appType)
{
this.appType = appType;
this.selfType = this.GetType();
}
}
[MessageHandler(AppType.Gate)]
public class CG_LoginMessageHandler
{
public void RunTask()
{
Console.WriteLine("登陆网关服务器");
}
}
[MessageHandler(AppType.DB)]
public class CD_RegisterMessageHandler
{
public void RunTask()
{
Console.WriteLine("注册数据库服务器");
}
}
class Program
{
static void Main(string[] args)
{
CD_RegisterMessageHandler registerMessageHandler = new CD_RegisterMessageHandler();
var messageAttr = (MessageHandlerAttribute)Attribute.GetCustomAttribute(
registerMessageHandler.GetType(), typeof(MessageHandlerAttribute));
Console.WriteLine(messageAttr.appType);
Console.WriteLine(messageAttr.selfType);
}
}
}
可能稍微修改代码就适合以上需求,正常来说Main函数里应该有个死循环,然后取用消息队列里的消息句柄类,通过特性附加的信息进行不同的处理(比如把消息分拨都其他服务器),这里只获取、打印出附加的特性信息,概念图应该如下所示:
可能分布式服务器的概念图是这样的,如果不符合逻辑的话或者不够严谨的话(随便画的),希望各位不要太过较真,可以理解大概意思即可。
4.总结
常用的内建特性表格:
特性 | 作用 |
---|---|
JsonProperty | JsonConvert.SerializeObject指定json格式的键值名称 |
Serializable | 可序列化 |
NonSerialized | 不可序列化,可在类内部变量使用,表面序列化时过滤掉它 |
DLLImport | 非托管代码实现的 |
WebMethod | 被作为web服务器对外暴露的方法 |
Required | 必须存在的字段 |
MaxLength(100) | 限制字符串、数组长度 |
DebuggerStepThrough | 在代码打断点调试过程中,不进入该方法,加在不可能有错误的地方,方便调试 |
-
Attribute是什么?
Attribute是一种可由用户自有定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。可以对类、以及C#程序集中的成员进行进一步的描述。简单地说,Attribute就是一种“附着物”,就像牡蛎吸附在船底或礁石上一样。 这些附着物的作用是为它们附着体追加上额外的信息(这些信息保存在附着物的体内),通过Ildasm可以发现特性通常会被附带构造,Attribute是程序代码一部分,它不会被编译器丢弃,而且还会被编译器编译进程序集(Assembly)的元数据里(Metadata)里。在程序运行时,可以随时从元数据(.NET的元数据是指程序集中的命名空间、类、方法、属性等信息,这些信息是可以通过Reflection读取出来的)中获取出这些附加信息,并可以决策程序的运行效果。 -
Attribute与注释的区别
注释是对程序源代码的说明,主要目的是给人看的,在程序被编译的时候会被编译器所丢弃,因此它丝毫不会影响到程序的执行。Attribute是给编译器看的,CSharp内建的特性可以达到很多有趣的效果,比如跳过调式、提示函数过期、编译开关效果等,自定义的特性也可以自行决定附加信息的用处。