C#特性学习与使用(为枚举定义Description)

时间:2022-03-23 03:15:41

C#特性

以前的时候,用过C#中的特性,但只是会用,什么原理,有什么用这些问题不清楚,今天就腾出时间,学习了一下。

C#中的特性使用Attribute描述。在使用时,就像是java中的批注一样。不过C#使用中括号。特性用来描述我们的数据。编译器能够识别这些特性,以附加信息的形式存放在生成的元数据中,供clr使用。

下边看一个简单的应用

static void Main(string[] args)
 {
	       DisplayRunningMsg();
            DisplayDebugMsg();
            Trace("方法执行到结尾了!!");
            Console.Read();

}
 [DllImport("User32.dll")]
 public static extern int MessageBox(int hParent, string msg, string Caption, int type);
        [Conditional("DEBUG")]
        private  static  void DisplayRunningMsg()
        {
            Console.WriteLine("This is debug");
            Console.WriteLine("开始运行Main子程序。当前时间是"+DateTime.Now);
        }

        [Conditional("DEBUG")]
        [Obsolete]
        private  static  void DisplayDebugMsg()
        {
            Console.WriteLine("该方法已经废弃啦!!!");
        }

DllImport特新允许我们引入一个外部的dll,下边做一个函数的声明,我们就可以调用了。

Conditional属性表示在该种条件下就执行下边的代码 所以[Conditional("DEBUG")]此种标识的方法就只有在调试的时候才会在执行。   [Obsolete]特性标记该方法已经废弃。

运行上述代码输出(在debug模式下)


C#特性学习与使用(为枚举定义Description)

看的出来程序执行了[Conditional("DEBUG")]标记的方法。如果我们debug改为release,那么再次执行


C#特性学习与使用(为枚举定义Description)

程序并没有执行上述方法。看的出来,由于特性[Conditional("DEBUG")]标记,是的在release模式下,代码并没有运行其标记的函数。那么,我们就可以利用这个做一个error trace,使其只在debug的模式下输出当前错误信息,包括行号,,方法名,位置等。这里要用到 stacktrace类。

Ok,说到这里,你应该对特性有了以最最基本的了解。

那么,究竟什么是特性呢?

其实特性也是一个类。比如[Conditional("DEBUG")],就是构造了以Conditional对象(调用构造方法public Conditional(string type), 对,DllImport("User32.dll")对应的也有一个类DllImport.

下边我们自定义一个特性,你就会明白很多。

首先需要定一个一个类 ,该类需要集成Attribute,使其成为一个特性。.NET约定特性类都已Attribute结尾。然后在该类中定义一下字段和属性,完成构造。

代码如下

[AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)]
    class TrackerAttribute:Attribute
    {
        
        private string opUsername;
        private string opName;
        private DateTime dateTime;
        private string note;

        public  TrackerAttribute(string  opUsername,string  opName,string date)
        {
            this.opUsername = opUsername;
            this.opName = opName;
            this.dateTime = DateTime.Parse(date);
        }

        //位置参数,通过构造函数传递值
        public string  OpUsername
        {
            get { return opUsername; }
        }

        public  string  OpName
        {
            get { return opName; }
        }

        public  DateTime DateTime
        {
            get { return dateTime; }
        }

        //命名参数,提供set
        public string  Note
        {
            get { return note; }
            set { note = value; }
        }

        public override string ToString()
        {
            return "操作人" + opUsername + "操作名" + opName + "时间" + dateTime + "备注" + note;
        }
    }

嗯,对,他和普通的类几乎没什么差别,只不过继承于Attribute。然后他本身又有一些特性。我们做逐一介绍

我们在类TrackerAttribute 定义了几个字段,完成了构造函数TrackerAttribute(string opUsername,string  opName,stringdate)

那么我么在使用的时候就需要写[Tracker(“opusername”,”opname”,”2011-10-2600:04”,note=”这是备注”)],嗯,是的,使用类型(参数值,参数值)的方法完成了该对象的构造,即调用了该类的构造函数。构造函数里与字段对应的参数叫做位置参数,因为写的时候必须位置一一与源构造函数相同,其他不通过构造函数传入参数传递的,叫做命名参数,使用字段名=字段值 的形式赋值。这样完成函数构造和一些属性的赋值。一般情况下,我们将位置参数提供get访问,而命名参数则提供get和set,因为位置参数已经能够同感哦构造函数访问赋值了。

这个特性类上边还有几个特性,AttributeTargets表示当前特性的作用范围,他是一个位标记的枚举,比如all,field,method,标记过后,智能在相应的地方做该特性书写。比如指定枚举是作用与字段,那么如果该特性写在类上边,就会报错。

如上,你的特性类就完成了。

这样你就可以在其他方法上做该特性的标记了。

我们定义了特性,最重要的还是要获得该特性中的值。下边是获得的方法

Type type = typeof(Program);
            object[] objects = type.GetCustomAttributes(false);
            foreach (var o in objects)
            {
                TrackerAttribute trackerAttribute = o as TrackerAttribute;
                if (trackerAttribute != null)
                    Console.WriteLine(trackerAttribute.ToString());
                else
                {
                    Console.WriteLine("获得对象为空");
                }
            }

type.GetCustomAttributes(false);该方法将会获得该类上的所有特性标记,返回的是一个object的数组,你可以遍历,然后转换为你的指定特性,访问相应字段即可。同样,你也可以通过type.getMethods()[0] 获得一个methodinfo对象,然后调用该methodinfo对象的GetCustomAttributes方法即可。

 

介绍了如上的这些,我们利用特性,实现为枚举增加一个获得其描述的功能。

例如定义枚举

MyEnummyenum=MyEnum.TypeA 调用myenum.ToDescription 可以得到字符串 类型A。

我们可以想到定一个描述特性,然后在各个枚举元素上,做该特性的标记,然后提供扩展方法,访问该特性,取得该特性值。

代码如下

枚举定义

public enum MyType
    {
        [Description("A类型")]
        TypeA,
        [Description("B类型")]
        TypeB,
        [Description("C类型")]
        TypeC
    }

特性类DescriptionAttribute定义如下

  [AttributeUsage(AttributeTargets.Field,AllowMultiple =true,Inherited = true)]
    class DescriptionAttribute:Attribute
    {
        private string description;
        public  string Description
        {
            get { return description; }
        }

        public  DescriptionAttribute(String description)
        {
            this.description = description;
        }
    }

指定该特性只用于字段,定义DescriptionAttribute(String description)构造函数。

Ok,现在还缺少枚举的ToDescription方法,C#中的枚举是不支持定义方法的。

我们可以为其做一个扩展方法

扩展方法需要一个静态类,参数前要加this ,同时也指定了被扩展的对象,调用时可使用扩展对像的实例调用,也可以使用该静态类来调用。详细内容可参考http://www.cnblogs.com/sunrack/articles/1073759.html

 

扩展类如下

 public  static class Extension
    {

        public  static string ToDescription(this MyType myEnum)
        {
            Type type = typeof (MyType);
            FieldInfo info= type.GetField(myEnum.ToString());
             DescriptionAttribute descriptionAttribute= info.GetCustomAttributes(typeof (DescriptionAttribute), true)[0] as DescriptionAttribute;
             if (descriptionAttribute != null)
                 return descriptionAttribute.Description;
             else
                 return type.ToString();
        }
    }


这样MyType就多了一个ToDescription的方法,返回的值就是对应的特性值。

在main方法中

            MyTypemyType = MyType.TypeB;

            Console.WriteLine(myType.ToDescription());

控制台输出 B类型 达到了我们想要的效果。

这个方法还是很有用的。

 

从上边可以看出,我们如果要为一些类添加一些附加的信息,1. 这些附加信息在现实意义与该对象并不是具有真正的对象与属性关系,2. 无法在原来的,里边添加字段,或者加入字段后很难处理。这两种情况之一,都可以使用特性。随后,在IL中看一下,掉用特性时,编译器都做了什么事。晚了,该睡了。


2011/10/31 补:

 

今天用IL DASM 工具,查看了一下生成的IL代码,取出其中部分。

枚举MyType定义如下

.class public auto ansi sealed caILStudy.MyType
       extends [mscorlib]System.Enum
{
  .field public specialname rtspecialname int32 value__
  .field public static literal valuetype caILStudy.MyType TypeA = int32(0x00000000)
  .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 41 E7 B1 BB E5 9E 8B 00 00 )             // ...A........
  .field public static literal valuetype caILStudy.MyType TypeB = int32(0x00000001)
  .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 42 E7 B1 BB E5 9E 8B 00 00 )             // ...B........
  .field public static literal valuetype caILStudy.MyType TypeC = int32(0x00000002)
  .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 43 E7 B1 BB E5 9E 8B 00 00 )             // ...C........
} // end of class caILStudy.MyType

看的出来,枚举在IL中,任然会被转换成为一个类,各个类型是其字段。然而特性的定义是custom instance,我的IL语言功底不行,只能解释到这里了。

查看main方法中的代码


 .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       14 (0xe)
    .maxstack  1
    .locals init ([0] valuetype caILStudy.MyType myType)
    IL_0000:  ldc.i4.1
    IL_0001:  stloc.0
    IL_0002:  ldloc.0
    IL_0003:  call       string caILStudy.Extension::ToDescription(valuetype caILStudy.MyType)
    IL_0008:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000d:  ret
  } // end of method Program::Main

看来,本质上仍然是调用扩展方法,将枚举参数传递进去,输出结果。Ok,就到这里吧。