C# 기능

高洛峰
高洛峰원래의
2016-10-13 14:51:131583검색

다른 사람에게 짧은 메시지를 보내는 방법이 있는 메시징 시스템이 있다면 생각해 보세요.

// title: 标题;author:作者;content:内容;receiverId:接受者Id 
public bool SendMsg(string title, string author, string content, int receiverId){ 
    // Do Send Action 
}

우리는 매개변수가 이렇게 하나씩 나열되어 있다는 것을 곧 발견했습니다. 메소드의 매개변수 목록은 매우 열악합니다. 짧은 메시지를 캡슐화하기 위해 Message 클래스를 정의한 다음 Message 객체를 메소드에 전달하는 것이 좋습니다.

public class Message{ 
    private string title; 
    private string author; 
    private string content; 
    private int receiverId; 
    // 略 
} 
public bool SendMsg(Messag msg){ 
    // Do some Action 
}

이번에는 아마도 교체해야 할 것입니다. 이전 메서드는 삭제되고 확장성이 더 뛰어난 SendMsg 메서드로 대체됩니다. 안타깝게도 이 프로그램 세트는 API 세트로 ​​출시될 수 있고 많은 클라이언트 프로그램이 이미 업데이트할 때 이전 SendMsg() 메서드의 이전 버전을 사용하고 있기 때문에 그렇게 할 수 없는 경우가 많습니다. 프로그램 메서드를 사용하면 이전 버전의 SendMsg() 메서드를 사용하는 클라이언트 프로그램이 작동하지 않습니다.

이때 어떻게 해야 할까요? 물론 메소드 오버로딩을 통해 할 수 있으므로 기존 SendMsg() 메소드를 삭제할 필요는 없습니다. 그러나 새로운 SendMsg()가 매개변수 전달을 최적화할 뿐만 아니라 알고리즘과 효율성을 포괄적으로 최적화한다면 우리는 이제 새로운 고성능 SendMsg() 메소드를 사용할 수 있음을 클라이언트 프로그램에 알리게 될 것입니다. 하지만 이때 클라이언트 프로그램은 새로운 SendMsg 메소드가 이미 존재한다는 사실을 알지 못하므로 어떻게 해야 할까요? 클라이언트 프로그램을 유지 관리하는 프로그래머에게 전화해서 알리거나 그에게 이메일을 보낼 수는 있지만 이는 분명 충분히 편리하지 않습니다. , 그리고 결국 프로젝트를 컴파일한 후 SendMsg() 메서드의 이전 버전에 대한 호출이 있는 한 컴파일러에 알림을 보내는 방법이 있습니다. 이를 수행하기 위해

.Net에서 기능을 사용할 수 있습니다. 특성은 어셈블리 및 해당 개체에 로드할 수 있는 개체입니다. 이러한 개체에는 어셈블리 자체, 모듈, 클래스, 인터페이스, 구조, 생성자, 메서드, 메서드 매개 변수 등이 포함됩니다. 특성이 로드된 개체를 특성이라고 합니다. 대상

속성을 ​​속성이라고 합니다. 일부 책에서는 "속성"으로 번역합니다. 일반적으로 get 및/또는 set 접근자 클래스를 포함하기 때문입니다. 멤버를 "속성"(영어 속성)이라고 부르므로 이 기사에서는 "속성"을 구별하기 위해 "속성"이라는 용어를 사용합니다.

이 예를 통해 이 기능이 위의 문제를 어떻게 해결하는지 살펴보겠습니다. 이전 SendMsg() 메서드에 Obsolete 기능을 추가하여 이 메서드가 더 이상 사용되지 않음을 컴파일러에 알리고 컴파일러가 이를 발견하면 프로그램에서 이 방법을 사용하는 부분이 Obsolete로 표시된 경우 경고 메시지가 표시됩니다.

namespace Attribute { 
 
    public class Message {} 
    
    public class TestClass { 
       // 添加Obsolete特性 
       [Obsolete("请使用新的SendMsg(Message msg)重载方法")] 
       public static void ShowMsg() { 
           Console.WriteLine("这是旧的SendMsg()方法"); 
       } 
 
       public static void ShowMsg(Message msg) { 
           Console.WriteLine("新SendMsg()方法"); 
       } 
 
    } 
 
    class Program { 
       static void Main(string[] args) { 
           TestClass.ShowMsg(); 
           TestClass.ShowMsg(new Message());          
       } 
    } 
}

이제 이 코드를 실행하면 컴파일러에서 경고가 표시됩니다. 경고 CS0618: "Attribute.TestClass.ShowMsg()"는 더 이상 사용되지 않습니다. "새 SendMsg(Message msg) 오버로드를 사용하십시오. 방법". 이 속성을 사용하면 컴파일러가 클라이언트 프로그램에 새 메서드를 사용할 수 있음을 알리는 경고 메시지를 표시하는 것을 볼 수 있습니다. 이런 방식으로 프로그래머는 이 경고 메시지를 본 후 새 SendMsg() 사용을 고려할 것입니다. 방법. .

위의 예를 통해 기능 사용 방법을 대략적으로 살펴보았습니다. 먼저 대괄호 "[]" 쌍이 있고 그 뒤에 왼쪽 대괄호 "["가 있고 그 뒤에 기능 이름이 옵니다. , 예를 들어 Obsolete과 같이 괄호 "()"가 표시됩니다. 일반 클래스와 달리 이러한 괄호는 생성자의 매개변수를 작성할 수 있을 뿐만 아니라 클래스의 속성에 값을 할당할 수도 있습니다. Obsolete의 예에서는 생성자 매개변수만 전달됩니다.

생성자 매개변수를 사용하세요. 매개변수의 순서는 생성자가 선언된 순서와 동일해야 합니다. 속성에서는 모두 위치 매개변수(Positional 매개변수)라고도 합니다. 명명된 매개변수(NamedParameter)라고도 합니다.

기능을 직접 정의하고 사용할 수 없다면 기능을 잘 이해할 수 없을 것 같습니다. 이제 직접 기능을 만들어 보겠습니다. 다음과 같은 매우 일반적인 요구 사항이 있다고 가정해 보겠습니다. 클래스 파일을 생성하거나 업데이트할 때 클래스가 언제, 누구에 의해 생성되었는지 표시해야 합니다. 향후 업데이트에서는 해당 클래스가 언제, 누구에 의해 업데이트되었는지도 표시해야 합니다. 녹음은 업데이트된 내용을 녹음할 필요가 없습니다. 과거에는 어떻게 하시겠습니까?

//更新:jayce, 2016-9-10, 修改 ToString()方法 
//更新:pop, 2016-9-18 
//创建:code, 2016-10-1 
public class DemoClass{ 
    // Class Body 
}

이것은 실제로 녹음할 수 있지만 언젠가는 우리가 원하는 것입니다. 이 레코드를 데이터베이스에 저장하여 백업하시겠습니까? 소스 파일을 하나씩 보고, 이 주석을 찾은 다음, 하나씩 데이터베이스에 삽입하시겠습니까?

위의 기능 정의를 통해 속성을 사용하여 유형(데이터 수정 여부, 생성 시기, 생성자를 포함하여 데이터를 설명하는 데이터. 이 데이터는 클래스, 메서드 또는 속성일 수 있음)에 메타데이터를 추가할 수 있습니다. 설명 유형에 사용할 수 있습니다. 글쎄, 이것은 속성이 유용해야 하는 곳입니다. 따라서 이 예에서 메타데이터는 주석 유형("업데이트" 또는 "만들기"), 수정자, 날짜, 설명 정보(선택 사항)여야 합니다. 속성의 대상 유형은 DemoClass 클래스입니다.

DemoClass 클래스에 첨부된 메타데이터에 대한 이해에 따라 먼저 메타데이터를 캡슐화하는 RecordAttribute 클래스를 생성합니다.

public class RecordAttribute {    
       private string recordType;      // 记录类型:更新/创建    
       private string author;          // 作者    
       private DateTime date;          // 更新/创建 日期    
       private string memo;         // 备注    
      
       // 构造函数,构造函数的参数在特性中也称为“位置参数”。    
       public RecordAttribute(string recordType, string author, string date) {    
          this.recordType = recordType;    
          this.author = author;    
          this.date = Convert.ToDateTime(date);    
       }    
      
       // 对于位置参数,通常只提供get访问器    
       public string RecordType {   get { return recordType; }   }    
       public string Author { get { return author; } }    
       public DateTime Date { get { return date; } }    
      
       // 构建一个属性,在特性中也叫“命名参数”    
       public string Memo {    
          get { return memo; }    
          set { memo = value; }    
       }    
   }

생성자의 매개변수 날짜는 하나의 상수여야 합니다. , 유형 유형 또는 상수 배열이므로 DateTime 유형을 직접 전달할 수 없습니다.

这个类不光看上去,实际上也和普通的类没有任何区别,显然不能它因为名字后面跟了个Attribute就摇身一变成了特性。那么怎样才能让它称为特性并应用到一个类上面呢?进行下一步之前,我们看看.Net内置的特性Obsolete是如何定义的:

namespace System {    
        [Serializable]    
        [AttributeUsage(6140, Inherited = false)]    
        [ComVisible(true)]    
        public sealed class ObsoleteAttribute : Attribute {    
       
           public ObsoleteAttribute();    
           public ObsoleteAttribute(string message);    
           public ObsoleteAttribute(string message, bool error);    
       
           public bool IsError { get; }    
           public string Message { get; }    
        }    
    }

首先,我们应该发现,它继承自Attribute类,这说明我们的 RecordAttribute 也应该继承自Attribute类。 (一个特性类与普通类的区别是:继承了Attribute类)

其次,我们发现在这个特性的定义上,又用了三个特性去描述它。这三个特性分别是:Serializable、AttributeUsage 和 ComVisible。Serializable特性我们前面已经讲述过,ComVisible简单来说是“控制程序集中个别托管类型、成员或所有类型对 COM 的可访问性”(微软给的定义)这里我们应该注意到:特性本身就是用来描述数据的元数据,而这三个特性又用来描述特性,所以它们可以认为是“元数据的元数据”(元元数据:meta-metadata)。(从这里我们可以看出,特性类本身也可以用除自身以外的其它特性来描述,所以这个特性类的特性是元元数据。)

因为我们需要使用“元元数据”去描述我们定义的特性 RecordAttribute,所以现在我们需要首先了解一下“元元数据”。这里应该记得“元元数据”也是一个特性,大多数情况下,我们只需要掌握 AttributeUsage就可以了,所以现在就研究一下它。我们首先看上面AttributeUsage是如何加载到ObsoleteAttribute特性上面的。

[AttributeUsage(6140, Inherited = false)]

然后我们看一下AttributeUsage的定义:

namespace System { 
    public sealed class AttributeUsageAttribute : Attribute { 
       public AttributeUsageAttribute(AttributeTargets validOn); 
 
       public bool AllowMultiple { get; set; } 
       public bool Inherited { get; set; } 
       public AttributeTargets ValidOn { get; } 
    } 
}

可以看到,它有一个构造函数,这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter) validOn,还有两个命名参数(Named Parameter)。注意ValidOn属性不是一个命名参数,因为它不包含set访问器,(是位置参数)。

这里大家一定疑惑为什么会这样划分参数,这和特性的使用是相关的。假如AttributeUsageAttribute 是一个普通的类,我们一定是这样使用的:

// 实例化一个 AttributeUsageAttribute 类 
AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class); 
usage.AllowMultiple = true;  // 设置AllowMutiple属性 
usage.Inherited = false;// 设置Inherited属性

但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么怎么办呢?微软的软件工程师们就想到了这样的办法:不管是构造函数的参数 还是 属性,统统写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用“属性=值”这样的格式,它们之间用逗号分隔。于是上面的代码就减缩成了这样:

[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]

可以看出,AttributeTargets.Class是构造函数参数(位置参数),而AllowMutiple 和 Inherited实际上是属性(命名参数)。命名参数是可选的。将来我们的RecordAttribute的使用方式于此相同。(为什么管他们叫参数,我猜想是因为它们的使用方式看上去更像是方法的参数吧。)假设现在我们的RecordAttribute已经OK了,则它的使用应该是这样的:

[RecordAttribute("创建","张子阳","2008-1-15",Memo="这个类仅供演示")]    
    public class DemoClass{    
        // ClassBody    
    }    
       
    //其中recordType, author 和 date 是位置参数,Memo是命名参数。

从AttributeUsage特性的名称上就可以看出它用于描述特性的使用方式。具体来说,首先应该是其所标记的特性可以应用于哪些类型或者对象。从上面的代码,我们看到AttributeUsage特性的构造函数接受一个 AttributeTargets 类型的参数,那么我们现在就来了解一下AttributeTargets。

AttributeTargets 是一个位标记,它定义了特性可以应用的类型和对象。

public enum AttributeTargets { 
 
    Assembly = 1,         //可以对程序集应用属性。 
    Module = 2,              //可以对模块应用属性。 
    Class = 4,            //可以对类应用属性。 
    Struct = 8,              //可以对结构应用属性,即值类型。 
    Enum = 16,            //可以对枚举应用属性。 
    Constructor = 32,     //可以对构造函数应用属性。 
    Method = 64,          //可以对方法应用属性。 
    Property = 128,           //可以对属性 (Property) 应用属性 (Attribute)。 
    Field = 256,          //可以对字段应用属性。 
    Event = 512,          //可以对事件应用属性。 
    Interface = 1024,            //可以对接口应用属性。 
    Parameter = 2048,            //可以对参数应用属性。 
    Delegate = 4096,             //可以对委托应用属性。 
    ReturnValue = 8192,             //可以对返回值应用属性。 
    GenericParameter = 16384,    //可以对泛型参数应用属性。 
    All = 32767,  //可以对任何应用程序元素应用属性。 
}

因为AttributeUsage是一个位标记,所以可以使用按位或“|”来进行组合。所以,当我们这样写时:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)

意味着既可以将特性应用到类上,也可以应用到接口上。

AllowMutiple 属性用于设置该特性是不是可以重复地添加到一个类型上(默认为false),就好像这样:

[RecordAttribute("更新","jayce","2016-1-20")] 
[RecordAttribute("创建","pop","2016-1-15",Memo="这个类仅供演示")] 
public class DemoClass{ 
// ClassBody 
}

Inherited 就更复杂一些了,假如有一个类继承自我们的DemoClass,那么当我们将RecordAttribute添加到DemoClass上时,DemoClass的子类也会获得该特性。而当特性应用于一个方法,如果继承自该类的子类将这个方法覆盖,那么Inherited则用于说明是否子类方法是否继承这个特性。

现在实现RecordAttribute应该是非常容易了,对于类的主体不需要做任何的修改,我们只需要让它继承自Attribute基类,同时使用AttributeUsage特性标记一下它就可以了(假定我们希望可以对类和方法应用此特性):

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true, Inherited=false)] 
public class RecordAttribute:Attribute { 
    // 略 
}

我们已经创建好了自己的自定义特性,现在是时候使用它了。

[Record("更新", "code", "2016-1-20", Memo = "修改 ToString()方法")]    
    [Record("更新", "jayce", "2016-1-18")]    
    [Record("创建", "pop", "2016-1-15")]    
    public class DemoClass {         
        public override string ToString() {    
           return "This is a demo class";    
        }    
    }    
       
    class Program {    
        static void Main(string[] args) {    
           DemoClass demo = new DemoClass();    
           Console.WriteLine(demo.ToString());    
        }    
    }

利用反射来查看 自定义特性信息 与 查看其他信息 类似,首先基于类型(本例中是DemoClass)获取一个Type对象,然后调用Type对象的GetCustomAttributes()方法,获取应用于该类型上的特性。当指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一个参数attributeType时,将只返回指定类型的特性,否则将返回全部特性;第二个参数指定是否搜索该成员的继承链以查找这些属性。  

class Program {     
    static void Main(string[] args) {    
          Type t = typeof(DemoClass);    
          Console.WriteLine("下面列出应用于 {0} 的RecordAttribute属性:" , t);    
      
          // 获取所有的RecordAttributes特性    
          object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false);    
      
          foreach (RecordAttribute record in records) {    
              Console.WriteLine("   {0}", record);    
              Console.WriteLine("      类型:{0}", record.RecordType);    
              Console.WriteLine("      作者:{0}", record.Author);    
              Console.WriteLine("      日期:{0}", record.Date.ToShortDateString());    
              if(!String.IsNullOrEmpty(record.Memo)){    
                 Console.WriteLine("      备注:{0}",record.Memo);    
              }    
          }    
       }    
   }


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:없음다음 기사:C#的异步回调函数