C#中Property和Attribute的区别实例详解
本文实例分析了C#中Property和Attribute的区别。分享给大家供大家参考。具体分析如下:
在C#中有两个属性,分别为Property和Attribute,两个的中文意思都有特性、属性之间,但是用法上却不一样,为了区别,本文暂把Property称为特性,把Attribute称为属性。
Attribute才是本文的主角,把它称为属性我觉得很恰当。属性的意思就是附属于某种事物上的,用来说明这个事物的各种特征的一种描述。而Attribute就是干这事的。它允许你将信息与你定义的C#类型相关联,作为类型的标注。这些信息是任意的,就是说,它不是由语言本身决定的,你可以随意建立和关联任何类型的任何信息。你可以作用属性定义设计时信息和运行时信息,甚至是运行时的行为特征。关键在于这些信息不仅可以被用户取出来作为一种类型的标注,它更可以被编译器所识别,作为编译时的一种附属条件参加程序的编译。
以下部分内容及代码来源于《C#技术揭秘》(InsideC#SencondEdition)
定义属性:
属性实际上是一个派生自System.Attribute基类的类。System.Attribute类含有几个用于访问和检查自定义属性的方法。尽管你有权将任何类定义为属性,但是按照惯例来说,从System.Attribute派生类是有意义的。示例如下:
publicenumRegHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
publicclassRegKeyAttribute:Attribute
{
publicRegKeyAttribute(RegHivesHive,StringValueName)
{
this.Hive=Hive;
this.ValueName=ValueName;
}
protectedRegHiveshive;
publicRegHivesHive
{
get{returnhive;}
set{hive=value;}
}
protectedStringvalueName;
publicStringValueName
{
get{returnvalueName;}
set{valueName=value;}
}
}
我们在这里添加了不同注册表的枚举、属性类的构造器以及两个特性(Property)。在定义属性时你可以做许许多多的事情,下面我们看看如何在运行时查询属性。要想在运行时查询类型或成员所附着的属性,必须使用反射
查询类属性:
假设你希望定义一个属性,这个属性定义了将在其上创建对象的远程服务器。如果没有这个属性,就要把此信息保存在一个常量中或是一个应用程序的资源文件中。通过使用属性,只需用以下方法标注出类的远程服务器名即可:
usingSystem;
namespaceQueryAttribs
{
publicenumRemoteServers
{
JEANVALJEAN,
JAVERT,
COSETTE
}
publicclassRemoteObjectAttribute:Attribute
{
publicRemoteObjectAttribute(RemoteServersServer)
{
this.server=Server;
}
protectedRemoteServersserver;
publicstringServer
{
get
{
returnRemoteServers.GetName(
typeof(RemoteServers),this.server);
}
}
}
[RemoteObject(RemoteServers.COSETTE)]
classMyRemotableClass
{
}
classTest
{
[STAThread]
staticvoidMain(string[]args)
{
Typetype=typeof(MyRemotableClass);
foreach(Attributeattrin
type.GetCustomAttributes(true))
{
RemoteObjectAttributeremoteAttr=
attrasRemoteObjectAttribute;
if(null!=remoteAttr)
{
Console.WriteLine(
"Createthisobjecton{0}.",
remoteAttr.Server);
}
}
Console.ReadLine();
}
}
}
运行结果为:
CreatthisobjectonCOSETTE。
注意:在这个例子中的属性类名具有Attribute后缀。但是,当我们将此属性附着给类型或成员时却不包括Attribute后缀。这是C#语言的设计者提供的简单方式。当编译器看到一个属性被附着给一个类型或成员时,它会搜索具有指定属性名的System.Attribute派生类。如果编译器没有找到匹配的类,它就在指定的属性名后面加上Attribute,然后再进行搜索。因此,常见的使用做法是将属性类名定义为以Attribute结尾,在使用时忽略名称的这一部分。以下的代码都采用这种命名方式。
查询方法属性:
在下面这个例子中,我们使用属性将方法定义为可事务化的方法,只要存在TransactionableAttribute属性,代码就知道具有这个属性的方法可以属于一个事务。
usingSystem;
usingSystem.Reflection;
namespaceMethodAttribs
{
publicclassTransactionableAttribute:Attribute
{
publicTransactionableAttribute()
{
}
}
classSomeClass
{
[Transactionable]
publicvoidFoo()
{}
publicvoidBar()
{}
[Transactionable]
publicvoidGoo()
{}
}
classTest
{
[STAThread]
staticvoidMain(string[]args)
{
Typetype=Type.GetType("MethodAttribs.SomeClass");
foreach(MethodInfomethodintype.GetMethods())
{
foreach(Attributeattrin
method.GetCustomAttributes(true))
{
if(attrisTransactionableAttribute)
{
Console.WriteLine(
"{0}istransactionable.",
method.Name);
}
}
}
Console.ReadLine();
}
}
}
运行结果如下:
Fooistransactionable.
Gooistransactionable.
查询字段属性:
假设有一个类含有一些字段,我们希望将它们的值保存进注册表。为此,可以使用以枚举值和字符串为参数的构造器定义一个属性,这个枚举值代表正确的注册表hive,字符串代表注册表值名称。在运行时可以查询字段的注册表键。
usingSystem;
usingSystem.Reflection;
namespaceFieldAttribs
{
publicenumRegHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
publicclassRegKeyAttribute:Attribute
{
publicRegKeyAttribute(RegHivesHive,StringValueName)
{
this.Hive=Hive;
this.ValueName=ValueName;
}
protectedRegHiveshive;
publicRegHivesHive
{
get{returnhive;}
set{hive=value;}
}
protectedStringvalueName;
publicStringValueName
{
get{returnvalueName;}
set{valueName=value;}
}
}
classSomeClass
{
[RegKey(RegHives.HKEY_CURRENT_USER,"Foo")]
publicintFoo;
publicintBar;
}
classTest
{
[STAThread]
staticvoidMain(string[]args)
{
Typetype=Type.GetType("FieldAttribs.SomeClass");
foreach(FieldInfofieldintype.GetFields())
{
foreach(Attributeattrin
field.GetCustomAttributes(true))
{
RegKeyAttributerka=
attrasRegKeyAttribute;
if(null!=rka)
{
Console.WriteLine(
"{0}willbesavedin"
+"{1}\\\\{2}",
field.Name,
rka.Hive,
rka.ValueName);
}
}
}
Console.ReadLine();
}
}
}
运行结果为:
FoowillbesavedinHKEY_CURRENT_USER\\Foo
大家可以看到,用属性来标注类、方法、字段,既可以把用户的自定义信息附属在实体上,又可以在运行时动态的查询。下面我将讲一些C#中默认的预定义属性,见下表:
预定义的属性有效目标说明
AttributeUsageClass指定另一个属性类的有效使用方式
CLSCompliant全部指出程序元素是否与CLS兼容
ConditionalMethod指出如果没有定义相关联的字符串,编译器就可以忽略对这个方法的任何调用
DllImportMethod指定包含外部方法的实现的DLL位置
STAThreadMethod(Main)指出程序的默认线程模型为STA
MTAThreadMethod(Main)指出程序的默认模型为多线程(MTA)
Obsolete除了Assembly、Module、Parameter和Return将一个元素标示为不可用,通知用户此元素将被从未来的产品
ParamArrayParameter允许单个参数被隐式地当作params(数组)参数对待
SerializableClass、Struct、enum、delegate指定这种类型的所有公共和私有字段可以被串行化
NonSerializedField应用于被标示为可串行化的类的字段,指出这些字段将不可被串行化
StructLayoutClass、struct指定类或结构的数据布局的性质,比如Auto、Explicit或sequential
ThreadStaticField(静态)实现线程局部存储(TLS)。不能跨多个线程共享给定的静态字段,每个线程拥有这个静态字段的副本
下面介绍几种常用的属性
1.[STAThread]和[MTAThread]属性
classClass1
{
[STAThread]
StaticvoidMain(string[]args)
{
}
}
使用STAThread属性将程序的默认线程模型指定为单线程模型。注意,线程模型只影响使用COMinterop的应用程序,将这个属性应用于不使用COMinterop的程序将不会产生任何效果。
2.AttributeUsage属性
除了用于标注常规C#类型的自定义属性以外,还可以使用AttributeUsage属性定义你使用这些属性的方式。文件记录的AttributeUsage属性调用用法如下:
[AttributeUsage(validon,AllowMutiple=allowmutiple,Inherited=inherited)]
Validon参数是AttributeTargets类型的,这个枚举值的定义如下:
publicenumAttributeTargets
{
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)]
publicclassSomethingAttribute:Attribute
{
publicSomethingAttribute(stringstr)
{
}
}
//如果AllowMultiple=false,此处会报错
[Something(“abc”)]
[Something(“def”)]
classMyclass
{
}
Inherited参数是继承的标志,它指出属性是否可以被继承。默认是false。
InheritedAllowMultiple结果
truefalse派生的属性覆盖基属性
truefalse派生的属性和基属性共存
代码示例:
usingSystem;
usingSystem.Reflection;
namespaceAttribInheritance
{
[AttributeUsage(
AttributeTargets.All,
AllowMultiple=true,
//AllowMultiple=false,
Inherited=true
)]
publicclassSomethingAttribute:Attribute
{
privatestringname;
publicstringName
{
get{returnname;}
set{name=value;}
}
publicSomethingAttribute(stringstr)
{
this.name=str;
}
}
[Something("abc")]
classMyClass
{
}
[Something("def")]
classAnother:MyClass
{
}
classTest
{
[STAThread]
staticvoidMain(string[]args)
{
Typetype=
Type.GetType("AttribInheritance.Another");
foreach(Attributeattrin
type.GetCustomAttributes(true))
//type.GetCustomAttributes(false))
{
SomethingAttributesa=
attrasSomethingAttribute;
if(null!=sa)
{
Console.WriteLine(
"CustomAttribute:{0}",
sa.Name);
}
}
}
}
}
当AllowMultiple被设置为false时,结果为:
CustomAttribute:def
当AllowMultiple被设置为true时,结果为:
CustomAttribute:def
CustomAttribute:abc
注意,如果将false传递给GetCustomAttributes,它不会搜索继承树,所以你只能得到派生的类属性。
3.Conditional属性
你可以将这个属性附着于方法,这样当编译器遇到对这个方法调用时,如果没有定义对应的字符串值,编译器就忽略这个调用。例如,以下方法是否被编译取决于是否定义了字符串“DEGUG”:
[Condition(“DEBUG”)]
publicvoidSomeDebugFunc()
{
Console.WriteLine(“SomeDebugFunc”);
}
usingSystem;
usingSystem.Diagnostics;
namespaceCondAttrib
{
classThing
{
privatestringname;
publicThing(stringname)
{
this.name=name;
#ifDEBUG
SomeDebugFunc();
#else
SomeFunc();
#endif
}
publicvoidSomeFunc()
{Console.WriteLine("SomeFunc");}
[Conditional("DEBUG")]
[Conditional("ANDREW")]
publicvoidSomeDebugFunc()
{Console.WriteLine("SomeDebugFunc");}
}
publicclassClass1
{
[STAThread]
staticvoidMain(string[]args)
{
Thingt=newThing("T1");
}
}
}
4.Obsolete属性
随着代码不断的发展,你很可以会有一些方法不用。可以将它们都删除,但是有时给它们加上适当的标注比删除它们更合适,例如:
usingSystem;
namespaceObsAttrib
{
classSomeClass
{
[Obsolete("Don'tuseOldFunc,useNewFuncinstead",true)]
publicvoidOldFunc(){Console.WriteLine("Oops");}
publicvoidNewFunc(){Console.WriteLine("Cool");}
}
classClass1
{
[STAThread]
staticvoidMain(string[]args)
{
SomeClasssc=newSomeClass();
sc.NewFunc();
//sc.OldFunc();//compilererror
}
}
}
我们将Obsolete属性的第二个参数设置为true,当调用时函数时编译器会产生一个错误。
E:\InsideC#\Code\Chap06\ObsAttrib\ObsAttrib\Class1.cs(20):'ObsAttrib.SomeClass.OldFunc()'已过时:'Don'tuseOldFunc,useNewFuncinstead'
5.DllImport和StructLayout属性
DllImport可以让C#代码调用本机代码中的函数,C#代码通过平台调用(platforminvoke)这个运行时功能调用它们。
如果你希望运行时环境将结构从托管代码正确地编组现非托管代码(或相反),那么需要为结构的声明附加属性。为了使结构参数可以被正确的编组,必须使用StructLayout属性声明它们,指出数据应该严格地按照声明中列出的样子进行布局。如果不这么做,数据将不能正确地被编组,而应用程序可能会出错。
usingSystem;
usingSystem.Runtime.InteropServices;//forDllImport
namespacenativeDLL
{
publicclassTest
{
//[DllImport("user32.dll")]//allthedefaultsareOK
[DllImport("user32",EntryPoint="MessageBoxA",
SetLastError=true,
CharSet=CharSet.Ansi,ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
publicstaticexternintMessageBoxA(
inth,stringm,stringc,inttype);
[StructLayout(LayoutKind.Sequential)]
publicclassSystemTime{
publicushortwYear;
publicushortwMonth;
publicushortwDayOfWeek;
publicushortwDay;
publicushortwHour;
publicushortwMinute;
publicushortwSecond;
publicushortwMilliseconds;
}
[DllImport("kernel32.dll")]
publicstaticexternvoidGetLocalTime(SystemTimest);
[STAThread]
publicstaticvoidMain(string[]args)
{
MessageBoxA(0,"HelloWorld","nativeDLL",0);
SystemTimest=newSystemTime();
GetLocalTime(st);
strings=String.Format("date:{0}-{1}-{2}",
st.wMonth,st.wDay,st.wYear);
stringt=String.Format("time:{0}:{1}:{2}",
st.wHour,st.wMinute,st.wSecond);
stringu=s+","+t;
MessageBoxA(0,u,"Now",0);
}
}
}
6.配件属性
当使用.NET产生任何类型的C#工程时,会自动的产生一个AssemblyInfo.cs源代码文件以及应用程序源代码文件。AssemblyInfo.cs中含有配件中代码的信息。其中的一些信息纯粹是信息,而其它信息使运行时环境可以确保惟一的命名和版本号,以供重用你的配件的客户代码使用。
7.上下文属性
.NET柜架还提供了另一种属性:上下文属性。上下文属性提供了一种截取机制,可以在类的实例化和方法调用之前和之后进行处理。这种功能用于对象远程调用,它是从基于COM的系统所用的COM+组件服务和MicrosoftTransactionServices(MTS)。
希望本文所述对大家的C#程序设计有所帮助。