【转帖】C#中Property和Attribute的区别

标签:

区别

c

property

attribute

丁丁丫丫

腾骧

it

分类:工作相关
本文来自优快云博客,转载请标明出处: http://blog.youkuaiyun.com/kldx5092/archive/2009/05/12/4170953.aspx

在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属性将程序的默认线程模型指定为单线程模型。注意,线程模型只影响使用COMinterop的应用程序,将这个属性应用于不使用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, useNewFunc instead'
 
5. DllImport和StructLayout属性
       DllImport可以让C#代码调用本机代码中的函数,C#代码通过平台调用(platforminvoke)这个运行时功能调用它们。
       如果你希望运行时环境将结构从托管代码正确地编组现非托管代码(或相反),那么需要为结构的声明附加属性。为了使结构参数可以被正确的编组,必须使用StructLayout属性声明它们,指出数据应该严格地按照声明中列出的样子进行布局。如果不这么做,数据将不能正确地被编组,而应用程序可能会出错。
using System;
usingSystem.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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值