一、引言
今天,我们来聊一下C#中的特性Attribute。何为特性Attribute?我们先看一个特性的使用例子,我们定义了一个Human类,但是由于某种原因该类不再被使用,但是我又不想将该类的代码注释或者删除掉,于是我们就可以通过给它赋予Obsolete特性来禁止别人使用:
-
[Obsolete("该类已经过时,不能再使用",true)]
-
public class Human
-
{
-
public string Name { get; set; }
-
public Human Child { get; set; }
-
}
当我们在代码中使用该类时,发现编译器会报错,并提示我们自定义的提示信息。
这是在main函数中使用Human类:
-
class Program
-
{
-
static void Main(string[] args)
-
{
-
Human human;
-
}
-
}
这是编译器报的错误:
我们在Human类前面添加的obsolete就是该类的一个特性,与之相对,我们接触最多的应该是类的属性Property,Human类的Name和Child就是其两个属性。特性本质就是一个类,Obsolete中传递的两个参数就是该类的构造函数所需的参数,第一个是在编译出错时编译器要提示的字符串,第二个是指是否 报错,如果为true编译时就会报错。特性除了可以放到类前面之外,也可以放到方法和属性的前面。
二、特性属于类而非属于对象
在很多的资料中,都把特性比作类在某一特性环境下的属性。比如对于Human类,把一个人放到学校中它就有年级等特性,把它放到公司中就有部门等特性,这种特性不是它与生既来的(与生既来的是属性Proerty),而是随着环境的不同而发生变化。虽然这种解释很形象,但是很容易让人发生误解——某一个对象具有特性!而实际上,特性是属于类而非一个对象,这点类似于类的静态成员,我们不能通过一个human实例获取Human类的特性,只能通过诸如以下的语句来获取其特性:
object[] attributes = typeof(Human).GetCustomAttributes(true);
其实,要想理解这个问题。我们需要了解特性的用途:特性不是给程序员用的,而是给编译器看的。它告诉编译器这个类具有何特性,在何种情况下应该作何处理!比如在上面的类型中,告诉编译器该类已经不能被使用了;再比如我们在类前面添加 [Serializable]特性,告诉编译器,该类支持序列化。
三、自定义特性
在前面的例子中,我们提到过,特性的本质其实就是一个类,这个类必须继承自Attribute基类,而且类名一般要求格式为“名称+Attribute”的形式。我们现在为Human类定义一个Help的属性,来告诉使用者Human类的信息:
-
public class HelpAttribute : Attribute
-
{
-
public Help(string info)
-
{
-
this.Info = info;
-
}
-
public string Info { get; private set; }
-
}
我们在使用时可以加Attribute或者不加Attribute,如下面的格式:
[Help("这是一个Human类")]
或者
[HelpAttribute("这是一个Human类")]
因为编译器在处理特性时会先查找特性名称,如果找不到就自动加上Attribute再次进行查找。
我们在主函数中查找类的特性并输出:
-
static void Main(string[] args)
-
{
-
object[] attributes = typeof(Human).GetCustomAttributes(true);
-
HelpAttribute attribute=attributes[0] as HelpAttribute;
-
Console.WriteLine(attribute.Info);
-
}
得到的结果如下:
四、使用转换类TypeConvertAttribute
接下来,定义一个用于进行类型转换的类,我们将字符串转换成human类对象:
-
public class StringToHumanConverter : TypeConverter
-
{
-
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
-
{
-
if (value is String)
-
{
-
Human h = new Human();
-
h.Name = value as String;
-
return h;
-
}
-
return base.ConvertFrom(context, culture, value);
-
}
-
}
接下来,将特性赋予Human类 :
-
[TypeConverterAttribute(typeof(StringToHumanConverter))]
-
public class Human
-
{
-
public string Name { get; set; }
-
public Human Child { get; set; }
-
}
我们定义的这个类,不能在cs代码中直接用来将字符串和Human类对象的隐式转换,诸如下面的用法是错误的:
Human human = str as Human;
我们想在cs代码中进行转换,只能定义一个StringToHumanConverter对象,调用其ConverFrom方法进行转换:
-
String str = "hyman";
-
StringToHumanConverter convert = new StringToHumanConverter();
-
Human human = (Human)convert.ConvertFrom(str);
但是这种转换可以在XMAL中直接使用:
-
<Window.Resources>
-
<local:Human x:Key="human" Child="tom"/>
-
</Window.Resources>
我们在后台代码中可以通过FindResource找到该资源,发现已经可以正常打印出其Child的Name,这是因为WPF的框架自动调用TypeConverter进行了转换:
-
private void button1_Click(object sender, RoutedEventArgs e)
-
{
-
Human human = (Human)this.FindResource("human");
-
MessageBox.Show(human.Child.Name);
-
}