特性和属性
【问】
1、特定就是属性吗?
2、以下结果输出什么?
[C#]
namespace Anytao.net
{
class MainClass
{
static void Main()
{
MainClass aa = new MainClass();
aa.CannotRun();
Console.ReadKey();
}
[Test("参数")]
public void CannotRun()
{
Console.WriteLine("执行CannotRun()方法");
}
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,Inherited = true)]
public class TestAttribute : System.Attribute
{
public TestAttribute(string message)
{
Console.WriteLine(message);
}
}
}
}
[VB.NET]
Namespace Anytao.net
Class MainClass
Public Shared Sub Main()
Dim aa As New MainClass()
aa.CannotRun()
Console.ReadKey()
End Sub
<Test("参数")> _
Public Sub CannotRun()
Console.WriteLine("执行CannotRun()方法")
End Sub
<AttributeUsage(AttributeTargets.[Class] Or AttributeTargets.Method, Inherited := True)> _
Public Class TestAttribute
Inherits System.Attribute
Public Sub New(message As String)
Console.WriteLine(message)
End Sub
End Class
End Class
End Namespace
【错误回答】
既然“特性”和“属性”都翻译成Attribute,那么应该是一样的。
输出message变量中的内容。
【正解】
初学者往往在看了一些书籍,或者是MSDN等地方的资料往往会把“特性”和“属性”划等号,其实两者完全不一样。“属性”是针对类变量而言,因为OOP设计原则告诉我们不该把私有变量对外公开,让外者直接访问;因此微软使用了“属性”这个东西来封装私有变量,对外进行交互。其实“属性”本质上也是普通的方法(成对的get和set)。只不过这个方法相对其它而言做得事情较为简单——只负责读取对应私有变量值作为输出,或者是接受数据并且给对应的私有变量赋值。同时,使用“属性”还有一个好处在于可以在赋值的时候先对要赋值数值进行验证,以确保其是否符合输入的条件等。比如入幼稚园年龄规定在3岁到6岁,那么你就可以这样做:
[C#]
public class Kid
{
private int _age=0;
public int Age
{
get {return _age;}
set
{
if (_age<3 || _age>6)
{
throw new Exception("无法入园,年龄不符合!");
}
_age=value;
}
}
}
[VB.NET]
Public Class Kid
Private _age As Integer = 0
Public Property Age() As Integer
Get
Return _age
End Get
Set
If _age < 3 OrElse _age > 6 Then
Throw New Exception("无法入园,年龄不符合!")
End If
_age = value
End Set
End Property
End Class
相对属性而言,“特性”是一种 “附加属性”,用于表示某个类,或者描述某个属性的额外信息。一个典型的例子是如果我们要使得一个类可以被“序列化”,我们应当在该类头部出加上“Serializable”特性——
[C#]
[Serializable]
public class Model
{
}
[VB.NET]
<Serializable>
Public class Model
{
}
又如——如果我们定义了一个类库,后来更新这个类库,在这个新版本中为考虑兼容或是安全等因素,某些方法我们已经不提倡用户使用了,我们可以直接使用系统特性Obsolate来加以说明——
[C#]
public class Model
{
[Obsolete("不可使用,该方法过期。")]
public void OldMethod()
{
//省略实现
}
}
[VB.NET]
Public Class Model
<Obsolete("不可使用,该方法过期。")> _
Public Sub OldMethod()
'省略实现
End Sub
End Class
明显地,如果你在主函数中使用了OldMethod,将会出现一个警告,同时编译器提示框也会告知此方法已经过期了。
第二问是一个自定义特性,其中该特性有一个构造函数,输出的话允许人为自定义。通常而言,一个类被“new”了(在为该类创建新实例的时候),应该会自动调用其构造函数;但是“特性类”不然——因为“特性”只是作为一种“附加属性”存在,是对原有类或者其中的某些方法、属性等的补充描述,所以不会被直接运行。
【总结】
1)“属性”是针对“私有变量”而言,提供对外访问的“接口模式”。
2)“特性”是对于类或者其内部方法、属性的补充描述,不属于“类”的一部分,所以不会和“类”一起运行。除非通过反射机制“激活”该类的特性并且使用代码运行某些特殊的功能。所以“特性”仅仅起到一个标识符的作用。
【拓展】
一、“特性”的定义和说明:
我们知道了“特性”不是“类”的一部分,是独立于“类”单独存在的一种特殊的标识符。除了系统提供的“Serializable”和“Obsolate”等一些类,我们是否可以自定义特性呢?答案当然 是肯定的,第二问其实正是考察了这一点,现在我们就深入下去看看关于自定义特性的一些必要知识。
从基本的特性定义(如第二问)我们可以知道“特性”的本质是一个继承Attribute的类,也可以有构造函数等(这些特性和一般类基本没有很大差别)。不过在这个自定义特性上有另一个特性“AttributeUsage”——这个特性中文直面翻译就是“特性使用”,主要用于限定该特性的“使用范围”、“是否可以被继承”和“是否允许多次出现”。下面逐一介绍这些内容:
1、使用范围:“AttributeTargets”是一个枚举类型,指明了该自定义特性可以被“放到”哪个地方起作用(比如像“Serializable”只能作用于类上,Obsolate只能作用于方法上等)。
比如第二问中的那个TestAttribute只能用于“类”上,你用于类中的某个方法或者属性将会导致一个编译错误。
2、多次出现(AllowMultiple):说明这个特性是否可以“重复”地多次。一般地,多个特性作用于某一个地方的写法有两种——
2.1) [特性1,特性2,……,特性N]
类(方法、属性)……
2.2) [特性1]
[特性2]
……………………
[特性N]
类(方法、属性)……
如果这个值是false(默认也是false),那么就意味着作用于某个特定的类、属性或者方法等上时,就会引发一个Duplicate (重复定义)特性的错误。
3、是否允许继承(AllowInherit):如果这个特性是作用于某些父类上,当有一个子类继承它的时候,子类是否可以获取与父类同样的特性。
这里需要强调一点——“特性”的“继承性”和“是否允许重复”无关。这是因为有些人可能会问——如果父类和子类都有同一个特性分别作用在他们上边,那么会出现什么情况呢?考察下列例子:
[C#]
[AttributeUsage(AttributeTargets.Class,Inherited=true,AllowMultiple=false)]
class RangeAttribute:Attribute
{
}
[Range]
public class Model
{
}
[Range]
public class S:Model
{
}
[VB.NET]
<AttributeUsage(AttributeTargets.[Class], Inherited := True, AllowMultiple := False)> _
Class RangeAttribute
Inherits Attribute
End Class
<Range> _
Public Class Model
End Class
<Range> _
Public Class S
Inherits Model
End Class
或许你会说——因为S继承于Model类,又因为Model上的“Range”特性被继承了,导致了S类有两个“Range”了?其实不然——如果AllowMultiple=false,相当于是“子类把父类的特性给屏蔽了”,反之,如果AllowMultiple=true,子类保留其自身和父类继承下的特性(子类归子类的Range,父类是父类的,两者毫无关系;而且是先“子类”后“父类”地方式调用)。下面给出严格证明:
I) 给出一个已经定义好的特性:
[C#]
[AttributeUsage(AttributeTargets.Class,Inherited=true,AllowMultiple=false)]
class RangeAttribute:Attribute
{
public RangeAttribute(string s)
{
Console.WriteLine(s);
}
}
[VB.NET]
<AttributeUsage(AttributeTargets.[Class], Inherited := True, AllowMultiple := False)> _
Class RangeAttribute
Inherits Attribute
Public Sub New(s As String)
Console.WriteLine(s)
End Sub
End Class
II) 定义一个Model和S类(其中S是Model的子类),套用此属性:
[C#]
[Range("父类")]
public class Model
{
}
[Range("子类")]
public class S:Model
{
}
[VB.NET]
<Range("父类")> _
Public Class Model
End Class
<Range("子类")> _
Public Class S
Inherits Model
End Class
3)最后在主函数中这样反射调用:
[C#]
foreach (var item in typeof(S).GetCustomAttributes(true)) ;
[VB.NET]
For Each item As var In GetType(S).GetCustomAttributes(True) Next
现在AllowMultiple=false,那么运行结果你会发现只输出“子类”(说明父类的那个“特性”完全被“子类”重写了,不会被调用)。如果改成true,则输出“子类”、“父类”两项。注意“GetCustomAttributes”方法,该方法必须至少接受一个参数以便指定是否获取从父类继承的特性。
二、“特性”的应用:
在“MVC”和“DynamicData”等多出场合,“模型验证”是“特性”表现的一个绝佳的舞台。今天我们在控制台中模拟类似类似功能:
1、 先定义一个特性类,如下:
[C#]
[AttributeUsage(AttributeTargets.Property,Inherited=true,AllowMultiple=true)]
class RangeAttribute:Attribute
{
public int MaxRange { get; set; }
public int MinRange { get; set; }
}
[VB.NET]
<AttributeUsage(AttributeTargets.[Property], Inherited := True, AllowMultiple := True)> _
Class RangeAttribute
Inherits Attribute
Public Property MaxRange() As Integer
Get
Return m_MaxRange
End Get
Set
m_MaxRange = Value
End Set
End Property
Private m_MaxRange As Integer
Public Property MinRange() As Integer
Get
Return m_MinRange
End Get
Set
m_MinRange = Value
End Set
End Property
Private m_MinRange As Integer
End Class
2、 编写一个通用验证方法,通过反射注意判定类的公共属性是否具备加载了特定的“Rage”特性,如果具备了,然后检测里边的数据是否在Max和Min的范围之内:
[C#]
static void ModelValidate(this Model m)
{
foreach (PropertyInfo item in m.GetType().GetProperties())
{
foreach (object attr in item.GetCustomAttributes(false))
{
if (attr is RangeAttribute)
{
//获取Max和Min的内容
RangeAttribute r = (RangeAttribute)attr;
int n = (int)item.GetValue(m, null);
if (n < r.MinRange || n > r.MaxRange)
{
throw new Exception("数组越界!");
}
}
}
}
}
[VB.NET]
<System.Runtime.CompilerServices.Extension> _
Private Shared Sub ModelValidate(m As Model)
For Each item As PropertyInfo In m.[GetType]().GetProperties()
For Each attr As Object In item.GetCustomAttributes(False)
If TypeOf attr Is RangeAttribute Then
'获取Max和Min的内容
Dim r As RangeAttribute = DirectCast(attr, RangeAttribute)
Dim n As Integer = CInt(item.GetValue(m, Nothing))
If n < r.MinRange OrElse n > r.MaxRange Then
Throw New Exception("数组越界!")
End If
End If
Next
Next
End Sub
3、 最后主函数中这样调用该静态方法:
[C#]
Model m = new Model();
m.Age = 31;
ModelValidate(m);
[VB.NET]
Dim m As New Model()
m.Age = 31
ModelValidate(m)
4、 运行结果,你就明白它抛出异常了。