C#の機能

高洛峰
高洛峰オリジナル
2016-10-13 14:51:131579ブラウズ

誰かに短いメッセージを送信するメソッドを備えたメッセージング システムがある場合について考えてみましょう:

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

メソッドのパラメータ リストにパラメータを 1 つずつリストすることはスケーラブルであることがすぐにわかりました。ショート メッセージをカプセル化する 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 メソッドに置き換える必要があります。残念ながら、更新時に古い SendMsg() を削除するだけでは、この一連のプログラムが API セットとしてリリースされる可能性があり、多くのクライアント プログラムがすでに古いバージョンの SendMsg() メソッドを使用しているため、それができないことがよくあります。プログラム メソッドを使用すると、古いバージョンの SendMsg() メソッドを使用するクライアント プログラムは動作しなくなります。

この時点で何をすべきでしょうか? もちろん、メソッドのオーバーロードを通じて実行できるため、古い SendMsg() メソッドを削除する必要はありません。しかし、新しい SendMsg() がパラメータの受け渡しを最適化するだけでなく、アルゴリズムと効率も包括的に最適化するのであれば、新しい高パフォーマンスの SendMsg() メソッドが利用可能になったことをクライアント プログラムに知らせたいと思うでしょう。しかし、この時点では、クライアント プログラムは新しい SendMsg メソッドが既に存在することを知りません。では、クライアント プログラムを管理しているプログラマに電話して伝えるか、電子メールを送信するか考えられますが、これでは明らかに十分ではありません。そして最終的には、プロジェクトをコンパイルすると、古いバージョンの SendMsg() メソッドへの呼び出しがある限り、コンパイラに通知される方法があります。

これを行うために .Net で利用できる機能があります。キャラクタリスティックとは、アセンブリにロードできるオブジェクトとそのオブジェクトです。これらのオブジェクトには、アセンブリ自体、モジュール、クラス、インターフェイス、構造体、コンストラクター、メソッド、メソッド パラメーターなどが含まれます。属性とともにロードされるオブジェクトは、英語では属性と呼ばれます。ターゲット

属性の名前は、一部の本では「属性」と訳されますが、他の本では「属性」と訳されます。これは、通常は get および/または set アクセサーが含まれるためです。を「プロパティ」(英語Property)と呼ぶので、この記事では「プロパティ」(Property)と区​​別して「プロパティ」という用語を使用します。

この例を使用して、機能が上記の問題をどのように解決するかを見てみましょう。Obsolete 機能を古い SendMsg() メソッドに追加して、このメソッドが廃止されたことをコンパイラーに通知し、コンパイラーがその場所があることを検出したときに、プログラム 「廃止」とマークされたこのメソッドを使用すると、警告メッセージが表示されます。

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());          
       } 
    } 
}

次に、このコードを実行すると、コンパイラが警告を発することがわかります: warning CS0618: "Attribute.TestClass.ShowMsg()" は廃止されました: "新しい SendMsg (メッセージ メッセージ) オーバーロードされたメソッドを使用してください。"この属性を使用すると、コンパイラがクライアント プログラムに新しいメソッドが使用可能であることを伝える警告メッセージを表示することがわかります。このようにして、プログラマはこの警告メッセージを見た後、新しい SendMsg() の使用を検討します。方法。 。

上記の例を通じて、機能の使用方法を大まかに見てきました。最初に一対の大括弧「[]」があり、左角括弧「[」の後に機能の名前が続きます(Obsoleteなど)。丸括弧「()」で囲みます。通常のクラスとは異なり、これらの括弧はコンストラクターのパラメーターを記述するだけでなく、クラスの属性に値を割り当てることもできます。Obsolete の例では、コンストラクターのパラメーターのみが渡されます。

コンストラクター パラメーターを使用します。パラメーターの順序は、コンストラクターが宣言された順序と同じである必要があります。同様に、属性パラメーターは、プロパティ内で位置パラメーター (位置パラメーター) とも呼ばれます。パラメーター)。

自分で機能を定義して使用することができないと、その機能をよく理解できないと思いますので、今度は自分で機能を構築してみましょう。このような非常に一般的な要件があるとします。クラス ファイルを作成または更新するときに、そのクラスがいつ、誰によって作成されたかを示す必要があり、将来の更新では、そのクラスがいつ、誰によって更新されたのかも示す必要があります。記録には、更新されたコンテンツを記録する必要はありません。以前なら、次のようにクラスにコメントを追加しますか?

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

これは確かに記録できますが、いつかこれらを記録したい場合は、データベースに保存します。バックアップ用にソース ファイルを 1 つずつ表示し、コメントを見つけてデータベースに 1 つずつ挿入しますか?

上記の特性の定義により、特性を使用して型を指定できることがわかりました。メタデータ (データが変更されたかどうか、いつ作成されたか、誰が作成したかなど、データを説明するデータ。このデータは、クラス、メソッド、または属性にすることができます。これらのメタデータは、型を説明するために使用できます)。ここでプロパティが役に立ちます。したがって、この例では、メタデータは、コメント タイプ (「更新」または「作成」)、修飾子、日付、コメント情報 (オプションかどうか) である必要があります。属性のターゲット タイプは 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; }    
       }    
   }

コンストラクターのパラメーター date は定数、Type 型、または定数配列である必要があることに注意してください。 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#的异步回调函数