C#中Property和Attribute的区别实例详解

内容摘要
本文实例分析了C#中Property和Attribute的区别。分享给大家供大家参考。具体分析如下:
在C#中有两个属性,分别为Property和Attribute,两个的中文意思都有特性、属性之间,但是用
文章正文

本文实例分析了C#中Property和Attribute的区别。分享给大家供大家参考。具体分析如下:

在C#中有两个属性,分别为Property和Attribute,两个的中文意思都有特性、属性之间,但是用法上却不一样,为了区别,本文暂把Property称为特性,把Attribute称为属性。

Attribute才是本文的主角,把它称为属性我觉得很恰当。属性的意思就是附属于某种事物上的,用来说明这个事物的各种特征的一种描述。而Attribute就是干这事的。它允许你将信息与你定义的C#类型相关联,作为类型的标注。这些信息是任意的,就是说,它不是由语言本身决定的,你可以随意建立和关联任何类型的任何信息。你可以作用属性定义设计时信息和运行时信息,甚至是运行时的行为特征。关键在于这些信息不仅可以被用户取出来作为一种类型的标注,它更可以被编译器所识别,作为编译时的一种附属条件参加程序的编译。

以下部分内容及代码来源于《C#技术揭秘》(Inside C# Sencond Edition)

定义属性:

属性实际上是一个派生自System.Attribute基类的类。System.Attribute类含有几个用于访问和检查自定义属性的方法。尽管你有权将任何类定义为属性,但是按照惯例来说,从System.Attribute派生类是有意义的。示例如下:

public enum RegHives
{
   HKEY_CLASSES_ROOT,
   HKEY_CURRENT_USER,
   HKEY_LOCAL_MACHINE,
   HKEY_USERS,
   HKEY_CURRENT_CONFIG
}
public class RegKeyAttribute : Attribute
{
   public RegKeyAttribute(RegHives Hive, String ValueName)
   {
    this.Hive = Hive;
    this.ValueName = ValueName;
   }
   protected RegHives hive;
   public RegHives Hive
   {
    get { return hive; }
    set { hive = value; }
   }
   protected String valueName;
   public String ValueName
   {
    get { return valueName; }
    set { valueName = value; }
   }
}

我们在这里添加了不同注册表的枚举、属性类的构造器以及两个特性(Property)。在定义属性时你可以做许许多多的事情,下面我们看看如何在运行时查询属性。要想在运行时查询类型或成员所附着的属性,必须使用反射

查询类属性:

假设你希望定义一个属性,这个属性定义了将在其上创建对象的远程服务器。如果没有这个属性,就要把此信息保存在一个常量中或是一个应用程序的资源文件中。通过使用属性,只需用以下方法标注出类的远程服务器名即可:

using System;
namespace QueryAttribs
{
  public enum RemoteServers
  {
   JEANVALJEAN,
   JAVERT,
   COSETTE 
  }
  public class RemoteObjectAttribute : Attribute
  {
   public RemoteObjectAttribute(RemoteServers Server)
   {
    this.server = Server;
   }
   protected RemoteServers server;
   public string Server
   {
    get
    {
     return RemoteServers.GetName(
      typeof(RemoteServers), this.server);
    }
   }
  }
  [RemoteObject(RemoteServers.COSETTE)]
  class MyRemotableClass
  {
  }
  class Test
  {
   [STAThread]
   static void Main(string[] args)
   {
    Type type = typeof(MyRemotableClass);
    foreach (Attribute attr in
     type.GetCustomAttributes(true))
    {
     RemoteObjectAttribute remoteAttr =
      attr as RemoteObjectAttribute;
     if (null != remoteAttr)
     {
     Console.WriteLine(
       "Create this object on {0}.",
       remoteAttr.Server);
     }
    }
    Console.ReadLine();
   }
  }
}

运行结果为:

Creat this object on COSETTE。

注意:在这个例子中的属性类名具有Attribute后缀。但是,当我们将此属性附着给类型或成员时却不包括Attribute后缀。这是C#语言的设计者提供的简单方式。当编译器看到一个属性被附着给一个类型或成员时,它会搜索具有指定属性名的System.Attribute派生类。如果编译器没有找到匹配的类,它就在指定的属性名后面加上Attribute,然后再进行搜索。因此,常见的使用做法是将属性类名定义为以Attribute结尾,在使用时忽略名称的这一部分。以下的代码都采用这种命名方式。

查询方法属性:

在下面这个例子中,我们使用属性将方法定义为可事务化的方法,只要存在TransactionableAttribute属性,代码就知道具有这个属性的方法可以属于一个事务。

using System;
using System.Reflection;
namespace MethodAttribs
{
  public class TransactionableAttribute : Attribute
  {
   public TransactionableAttribute()
   {
   }
  }
  class SomeClass
  {
   [Transactionable]
   public void Foo()
   {}
   public void Bar()
   {}
   [Transactionable]
   public void Goo()
   {}
  }
  class Test
  {
   [STAThread]
   static void Main(string[] args)
   {
    Type type = Type.GetType("MethodAttribs.SomeClass");
    foreach (MethodInfo method in type.GetMethods())
    {
     foreach (Attribute attr in
      method.GetCustomAttributes(true))
     {
      if (attr is TransactionableAttribute)
      {
       Console.WriteLine(
        "{0} is transactionable.",
        method.Name);
      }
     }
    }
    Console.ReadLine();
   }
  }
}

运行结果如下:

Foo is transactionable.
Goo is transactionable.
 
查询字段属性:

假设有一个类含有一些字段,我们希望将它们的值保存进注册表。为此,可以使用以枚举值和字符串为参数的构造器定义一个属性,这个枚举值代表正确的注册表hive,字符串代表注册表值名称。在运行时可以查询字段的注册表键。

using System;
using System.Reflection;
namespace FieldAttribs
{
  public enum RegHives
  {
   HKEY_CLASSES_ROOT,
   HKEY_CURRENT_USER,
   HKEY_LOCAL_MACHINE,
   HKEY_USERS,
   HKEY_CURRENT_CONFIG
  }
  public class RegKeyAttribute : Attribute
  {
   public RegKeyAttribute(RegHives Hive, String ValueName)
   {
    this.Hive = Hive;
    this.ValueName = ValueName;
   }
   protected RegHives hive;
   public RegHives Hive
   {
    get { return hive; }
    set { hive = value; }
   }
   protected String valueName;
   public String ValueName
   {
    get { return valueName; }
    set { valueName = value; }
   }
  }
  class SomeClass
  {
   [RegKey(RegHives.HKEY_CURRENT_USER, "Foo")]
   public int Foo;
 
   public int Bar;
  }
  class Test
  {
   [STAThread]
   static void Main(string[] args)
   {
    Type type = Type.GetType("FieldAttribs.SomeClass");
    foreach (FieldInfo field in type.GetFields())
    {
     foreach (Attribute attr in
      field.GetCustomAttributes(true))
     {
      RegKeyAttribute rka =
       attr as RegKeyAttribute;
      if (null != rka)
      {
       Console.WriteLine(
        "{0} will be saved in"
        + " {1}\\\\{2}",
        field.Name,
        rka.Hive,
        rka.ValueName);
      }
     }
    }
    Console.ReadLine();
   }
  }
}

运行结果为:

Foo will be saved in HKEY_CURRENT_USER\\Foo

大家可以看到,用属性来标注类、方法、字段,既可以把用户的自定义信息附属在实体上,又可以在运行时动态的查询。下面我将讲一些C#中默认的预定义属性,见下表:

预定义的属性 有效目标 说明
AttributeUsage Class 指定另一个属性类的有效使用方式
CLSCompliant 全部 指出程序元素是否与CLS兼容
Conditional Method 指出如果没有定义相关联的字符串,编译器就可以忽略对这个方法的任何调用
DllImport Method 指定包含外部方法的实现的DLL位置
STAThread Method(Main) 指出程序的默认线程模型为STA
MTAThread Method(Main) 指出程序的默认模型为多线程(MTA)
Obsolete 除了Assembly、Module、Parameter和Return 将一个元素标示为不可用,通知用户此元素将被从未来的产品
ParamArray Parameter 允许单个参数被隐式地当作params(数组)参数对待
Serializable Class、Struct、enum、delegate 指定这种类型的所有公共和私有字段可以被串行化
NonSerialized Field 应用于被标示为可串行化的类的字段,指出这些字段将不可被串行化
StructLayout Class、struct 指定类或结构的数据布局的性质,比如Auto、Explicit或sequential
ThreadStatic Field(静态) 实现线程局部存储(TLS)。不能跨多个线程共享给定的静态字段,每个线程拥有这个静态字段的副本

下面介绍几种常用的属性

1.[STAThread]和[MTAThread]属性

class Class1
{
  [STAThread]
  Static void Main( string[] args )
  {
  }
}

使用STAThread属性将程序的默认线程模型指定为单线程模型。注意,线程模型只影响使用COM interop的应用程序,将这个属性应用于不使用COM interop的程序将不会产生任何效果。

2. AttributeUsage属性

除了用于标注常规C#类型的自定义属性以外,还可以使用AttributeUsage属性定义你使用这些属性的方式。文件记录的AttributeUsage属性调用用法如下:

[AttributeUsage( validon , AllowMutiple = allowmutiple , Inherited = inherited )]
Validon参数是AttributeTargets类型的,这个枚举值的定义如下:
public enum AttributeTargets
{
  Assembly = 0x0001,
  Module = 0x0002,
  Class = 0x0004,
  Struct = 0x0008,
  Enum = 0x0010,
  Constructor = 0x0020,
  Method = 0x0040,
  Property = 0x0080,
  Field = 0x0100,
  Event = 0x200,
  Interface = 0x400,
  Parameter = 0x800,
  Delegate = 0x1000,
  All = Assembly | Module | Class | Struct | Enum | Constructor| Method | Property|     Filed| Event| Interface | Parameter | Deleagte ,
  ClassMembers = | Class | Struct | Enum | Constructor | Method | Property | Field |     Event | Delegate | Interface 
}

AllowMultiple决定了可以在单个字段上使用某个属性多少次,在默认情况下,所有的属性都是单次使用的。示例如下:

[AttributeUsage( AttributeTargets.All , AllowMultiple = true )]
public class SomethingAttribute : Attribute
{
  public SomethingAttribute( string str )
  {
  }
}
//如果AllowMultiple = false , 此处会报错
[Something(“abc”)]
[Something(“def”)]
class Myclass
{
}

Inherited参数是继承的标志,它指出属性是否可以被继承。默认是false。
Inherited AllowMultiple 结果
true false 派生的属性覆盖基属性
true false 派生的属性和基属性共存

代码示例:

using System;
using System.Reflection;
namespace AttribInheritance
{
  [AttributeUsage(
   AttributeTargets.All,
   AllowMultiple=true,
//  AllowMultiple=false,
   Inherited=true
  )]
  public class SomethingAttribute : Attribute
  {
   private string name;
   public string Name
   {
    get { return name; }
    set { name = value; }
   }
   public SomethingAttribute(string str)
   {
    this.name = str;
   }
  }
  [Something("abc")]
  class MyClass
  {
  }
  [Something("def")]
  class Another : MyClass
  {
  }
  class Test
  {
   [STAThread]
   static void Main(string[] args)
   {
    Type type =
     Type.GetType("AttribInheritance.Another");
    foreach (Attribute attr in
     type.GetCustomAttributes(true))
//    type.GetCustomAttributes(false))
    {
     SomethingAttribute sa =
      attr as SomethingAttribute;
     if (null != sa)
     {
     Console.WriteLine(
       "Custom Attribute: {0}",
       sa.Name);
     }
    }
 
   }
  }
}

当AllowMultiple被设置为false时,结果为:
Custom Attribute : def
当AllowMultiple被设置为true时,结果为:
Custom Attribute : def
Custom Attribute : abc

注意,如果将false传递给GetCustomAttributes,它不会搜索继承树,所以你只能得到派生的类属性。
 
3.Conditional 属性

你可以将这个属性附着于方法,这样当编译器遇到对这个方法调用时,如果没有定义对应的字符串值,编译器就忽略这个调用。例如,以下方法是否被编译取决于是否定义了字符串“DEGUG”:

[Condition(“DEBUG”) ]
public void SomeDebugFunc()
{
  Console.WriteLine(“SomeDebugFunc”);
}
using System;
using System.Diagnostics;
namespace CondAttrib
{
  class Thing
  {
   private string name;
   public Thing(string name)
   {
    this.name = name;
    #if DEBUG
     SomeDebugFunc();
    #else
     SomeFunc();
    #endif
   }
   public void SomeFunc()
    { Console.WriteLine("SomeFunc"); }
   [Conditional("DEBUG")]
   [Conditional("ANDREW")]
   public void SomeDebugFunc()
    { Console.WriteLine("SomeDebugFunc"); }
  }
  public class Class1
  {
   [STAThread]
   static void Main(string[] args)
   {
    Thing t = new Thing("T1");
   }
  }
}

4. Obsolete 属性

随着代码不断的发展,你很可以会有一些方法不用。可以将它们都删除,但是有时给它们加上适当的标注比删除它们更合适,例如:

using System;
namespace ObsAttrib
{
  class SomeClass
  {
   [Obsolete("Don't use OldFunc, use NewFunc instead", true)]
   public void OldFunc( ) { Console.WriteLine("Oops"); }
 
   public void NewFunc( ) { Console.WriteLine("Cool"); }
  }
  class Class1
  {
   [STAThread]
   static void Main(string[] args)
   {
    SomeClass sc = new SomeClass();
    sc.NewFunc();
//   sc.OldFunc(); // compiler error
   }
  }
}

我们将Obsolete属性的第二个参数设置为true,当调用时函数时编译器会产生一个错误。

E:\InsideC#\Code\Chap06\ObsAttrib\ObsAttrib\Class1.cs(20): 'ObsAttrib.SomeClass.OldFunc()' 已过时: 'Don't use OldFunc, use NewFunc instead'
 
5. DllImport和StructLayout属性

DllImport可以让C#代码调用本机代码中的函数,C#代码通过平台调用(platform invoke)这个运行时功能调用它们。

如果你希望运行时环境将结构从托管代码正确地编组现非托管代码(或相反),那么需要为结构的声明附加属性。为了使结构参数可以被正确的编组,必须使用StructLayout属性声明它们,指出数据应该严格地按照声明中列出的样子进行布局。如果不这么做,数据将不能正确地被编组,而应用程序可能会出错。

using System;
using System.Runtime.InteropServices; // for DllImport
namespace nativeDLL
{
  public class Test
  {
//  [DllImport ("user32.dll")] // all the defaults are OK
   [DllImport("user32", EntryPoint="MessageBoxA",
    SetLastError=true,
    CharSet=CharSet.Ansi, ExactSpelling=true,
    CallingConvention=CallingConvention.StdCall)]
   public static extern int MessageBoxA (
    int h, string m, string c, int type);
   [StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
    public ushort wYear;
    public ushort wMonth;
    public ushort wDayOfWeek;
    public ushort wDay;
    public ushort wHour;
    public ushort wMinute;
    public ushort wSecond;
    public ushort wMilliseconds;
   }
   [DllImport ("kernel32.dll")]
   public static extern void GetLocalTime(SystemTime st);
   [STAThread]
   public static void Main(string[] args)
   {
    MessageBoxA(0, "Hello World", "nativeDLL", 0);
    SystemTime st = new SystemTime();
    GetLocalTime(st);
    string s = String.Format("date: {0}-{1}-{2}",
     st.wMonth, st.wDay, st.wYear);
    string t = String.Format("time: {0}:{1}:{2}",
     st.wHour, st.wMinute, st.wSecond);
    string u = s + ", " + t;
    MessageBoxA(0, u, "Now", 0);
   }
  }
}

6. 配件属性

当使用.NET产生任何类型的C#工程时,会自动的产生一个AssemblyInfo.cs源代码文件以及应用程序源代码文件。AssemblyInfo.cs中含有配件中代码的信息。其中的一些信息纯粹是信息,而其它信息使运行时环境可以确保惟一的命名和版本号,以供重用你的配件的客户代码使用。
 
7. 上下文属性

.NET柜架还提供了另一种属性:上下文属性。上下文属性提供了一种截取机制,可以在类的实例化和方法调用之前和之后进行处理。这种功能用于对象远程调用,它是从基于COM的系统所用的COM+组件服务和Microsoft Transaction Services(MTS)。

希望本文所述对大家的C#程序设计有所帮助。


代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!