动态属性与类型描述符的使用

在为产品原型设计新功能时,意识到展示如何使用TypeDescriptionProvider属性及其相关类会是一个好主意。本文将通过一个示例演示应用程序来说明这一点,该应用程序包含一个对象列表和一个显示所选对象属性的属性网格。在这个演示中,假设始终有两种类型的对象——一种代表书籍,另一种代表电影。这两种对象之间唯一的共同属性是Name属性。

书籍对象示例

书籍对象可能包含以下属性:

  • Name:书籍的标题
  • Amazon Rank:在亚马逊上的销售排名
  • Author:书籍的作者
  • HardCover:是否提供精装版

电影对象示例

电影对象可能包含以下属性:

  • Name:电影的名称
  • Director:电影的导演
  • Duration:电影的时长
  • Rating:电影的评级
  • Release Date:电影的发布日期

为了支持新对象类型的添加以及现有对象类型的属性动态添加(包括运行时),不能仅仅依赖于频繁添加新类或修改现有类。这时,使用TypeDescriptionProvider就显得非常有用。本文的剩余部分将解释如何使用这种方法来实现需求。

技术实现

首先,实现一个枚举来表示对象的类型。当添加新的对象类型时,会将其添加到这个枚举中。

public enum TitleCategory { Book, Movie }

接下来,添加一个名为Title的密封类,它定义了类型描述符提供者,并提供了默认属性。

[TypeDescriptionProvider(typeof(TitleTypeDescriptionProvider))] sealed class Title { public Title(String name, TitleCategory category) { this.Name = name; this.Category = category; } public String Name { get; set; } [Browsable(false)] public TitleCategory Category { get; private set; } public override string ToString() { return Name; } private Dictionary customFieldValues = new Dictionary(); public Object this[String fieldName] { get { Object value = null; customFieldValues.TryGetValue(fieldName, out value); return value; } set { customFieldValues[fieldName] = value; } } }

除了Name属性外,还有一个Category属性,用于指定对象的类型。此外,还会看到有一个字典用于自定义属性值,以及一个索引器,可用于获取和设置任何对象上的自定义属性。注意类上的TypeDescriptionProvider属性,它指定了TitleTypeDescriptionProvider作为类型描述符提供者。

自定义属性字段的表示

现在,可以编写TitleTypeDescriptionProvider类:

class TitleTypeDescriptionProvider : TypeDescriptionProvider { private static TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(Title)); public TitleTypeDescriptionProvider() : base(defaultTypeProvider) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { ICustomTypeDescriptor defaultDescriptor = base.GetTypeDescriptor(objectType, instance); return instance == null ? defaultDescriptor : new TitleCustomTypeDescriptor(defaultDescriptor, instance); } }

这里的重要方法是GetTypeDescriptor重写。当实例为null时返回默认描述符,否则返回一个TitleCustomTypeDescriptor对象。TitleCustomTypeDescriptor编写如下:

class TitleCustomTypeDescriptor : CustomTypeDescriptor { public TitleCustomTypeDescriptor(ICustomTypeDescriptor parent, object instance) : base(parent) { Title title = (Title)instance; customFields.AddRange(CustomFieldsGenerator.GenerateCustomFields(title.Category).Select(f => new CustomFieldPropertyDescriptor(f)).Cast()); } private List customFields = new List(); public override PropertyDescriptorCollection GetProperties() { return new PropertyDescriptorCollection(base.GetProperties().Cast().Union(customFields).ToArray()); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return new PropertyDescriptorCollection(base.GetProperties(attributes).Cast().Union(customFields).ToArray()); } }

这个类的核心是根据Category属性为对象生成自定义字段的列表。然后,在GetProperties重写中,将这些属性(字段)添加到默认列表中。这里的设计动态性得以体现,它允许动态地向对象类型添加新属性。对于演示,有一个CustomFieldsGenerator类,它返回动态字段。

动态字段的生成

CustomFieldsGenerator类如下所示:

internal static IEnumerable GenerateCustomFields(TitleCategory category) { List customFields = new List(); switch (category) { case TitleCategory.Book: customFields.Add(new CustomField("Author", typeof(String))); customFields.Add(new CustomField("HardCover", typeof(bool))); customFields.Add(new CustomField("Amazon Rank", typeof(int))); break; case TitleCategory.Movie: customFields.Add(new CustomField("Director", typeof(String))); customFields.Add(new CustomField("Rating", typeof(MovieRating))); customFields.Add(new CustomField("Duration", typeof(TimeSpan))); customFields.Add(new CustomField("Release Date", typeof(DateTime))); break; } return customFields; }

每个字段都使用CustomFieldPropertyDescriptor类存储,该类负责建立自定义属性的行为。

自定义属性描述符

CustomFieldPropertyDescriptor类如下所示:

class CustomFieldPropertyDescriptor : PropertyDescriptor { public CustomField CustomField { get; private set; } public CustomFieldPropertyDescriptor(CustomField customField) : base(customField.Name, new Attribute[0]) { CustomField = customField; } public override bool CanResetValue(object component) { return false; } public override Type ComponentType { get { return typeof(Title); } } public override object GetValue(object component) { Title title = (Title)component; return title[CustomField.Name] ?? (CustomField.DataType.IsValueType ? (Object)Activator.CreateInstance(CustomField.DataType) : null); } public override bool IsReadOnly { get { return false; } } public override Type PropertyType { get { return CustomField.DataType; } } public override void ResetValue(object component) { throw new NotImplementedException(); } public override void SetValue(object component, object value) { Title title = (Title)component; title[CustomField.Name] = value; } public override bool ShouldSerializeValue(object component) { return false; } }

这里的重要方法是GetValue和SetValue重写。使用Title类的索引器来实现这两种方法。注意,如果Title对象上尚未设置动态字段,则还会根据对象的类型返回一个默认值。还请注意PropertyType属性——这使能够为特定字段指定确切的数据类型。例如,电影评级是一个名为MovieRating的枚举类型,属性网格会尊重这一点。

电影评级枚举

enum MovieRating { G, PG, PG13, R, NC17 }

这就是全部内容。不仅仅是属性网格会尊重类型描述符,任何支持数据绑定的控件都会如预期工作。因此,可以将此传递给数据网格视图或第三方数据可视化控件,一切都会正常工作。请注意,在代码中,不能以常规方式访问动态属性,而必须通过索引器访问,如下所示:

Title title = new Title(name, TitleCategory.Movie); title["Director"] = director; title["Rating"] = rating; title["Duration"] = duration; title["Release Date"] = releaseDate;
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485