Effective C# Item 24: Prefer Declarative to Imperative Programming
声明式编程是一种简洁的程序行为描述方式。声明式编程让我们可以通过使用声明来达到定义程序行为的目的。在C#或者其它的编程语言中,命令式编程是最为常见的:我们通过编写方法来定义程序的行为。我们可以通过C#中的属性来使用声明式编程。我们可以为类,方法,属性和数据成员声明属性,在运行时.Net会自动为我们添加这些声明的行为。这种声明方法比命令式编程更简单,也更容易理解。
让我们从一个简单的例子开始。当我们编写第一个ASP.Net的Web Service时,设计器会自动生成下面一段代码:
public string HelloWorld()
{
return "Hello World";
}
在HelloWorld()方法上有一个[WebMethod]方法。它声明了HelloWorld是一个Web Service方法。在运行时,ASP.Net会对此属性作出响应,创建WSDL(Web服务描述语言)文档。另外我们还可以通过ASP.Net动态创建的HTML页在IE中测试我们的Web服务。上述这些动作都表现在WebMethod属性中。我们通过声明这种属性,就可以在运行时获得所期望的支持。使用这种属性可以提高我们的编程效率并减少可能发生的错误。
在ASP.Net运行时使用反射来检测类中的WebMethod。ASP.Net可以在运行时自动添加所需的代码将这些方法转变为WebMethod。
[WebMethod]属性只是.Net类库中定义的大量属性中的一个。这些属性可以帮助我们更快的完成编程工作。例如有些属性可以帮助我们创建序列化类型[Serializable],还有可以控制条件编译[Conditional]。我们应当更多的使用这些属性,而不是自己编写代码来达到目的。
如果预定义的属性不能满足我们的要求,我们也可以通过自定义属性和反射机制来创建我们自己的声明式编程结构。举例来说,我们希望创建一个可以设置默认排序的属性,通过使用这个属性,我们可以将该类中的某个属性设定为默认的排序比较项。下例中是我们希望达到的自定义属性的效果:
public class Customer
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
private string _balance;
public string CurrentBalance
{
get
{
return _balance;
}
}
}
在上例的声明中,Customers类的对象在任何集合中的默认排序应当根据Name的大小顺序来确定。DefaultSort属性并不是.Net Framework中已有的。要实现这种效果,我们首先要创建一个DefaultSortAttribute类:
public class DefaultSortAttribute : Attribute
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public DefaultSortAttribute(string name)
{
_name = name;
}
}
我们仍然需要为DefaultSort属性写一段为集合中对象排序的代码。我们可以使用反射来找到需要的属性并且对它们进行比较。这样做的好处在于我们只需要写一遍代码。
接下来我们需要创建一个实现IComparer接口的类。IComparer接口有一个CompareTo()方法可以用来比较两个给定的对象。通过它我们可以定义排序的规则。这个类通过构造函数来确定使用哪一个默认属性来进行比较排序。Compare方法对相隔对象的默认排序属性进行比较。
{
private readonly PropertyDescriptor _sortProp;
private readonly bool _reverse = false;
public GenericComparer(Type t)
: this(t, false)
{
}
public GenericComparer(Type t, bool reverse)
{
_reverse = reverse;
object[] a = t.GetCustomAttributes(typeof(DefaultSortAttribute), reverse);
if (a.Length > 0)
{
DefaultSortAttribute sortName = a[0] as DefaultSortAttribute;
string name = sortName.Name;
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(t);
if (props.Count > 0)
{
foreach (PropertyDescriptor p in props)
{
if (p.Name == name)
{
_sortProp = p;
break;
}
}
}
}
}
IComparer 成员#region IComparer 成员
int IComparer.Compare(object left, object right)
{
if ((left == null) && (right == null))
{
return 0;
}
if (left == null)
{
return -1;
}
if (right == null)
{
return 1;
}
if (_sortProp == null)
{
return 0;
}
IComparable lField = _sortProp.GetValue(left) as IComparable;
IComparable rField = _sortProp.GetValue(right) as IComparable;
int rVal = 0;
if (lField == null)
{
if (rField == null)
{
return 0;
}
else
{
return -1;
}
}
rVal = lField.CompareTo(rField);
return (_reverse) ? -rVal : rVal;
}
#endregion
}
对于任何声明有DefaultSort属性的类,GenericComparer都可以使用其默认排序属性对其进行排序。
在实现GenericComparer时我们使用了诸如反射之类的技巧。这样的好处在于我们只需要写一遍代码就可以适用于各种情况。我们只需要为类添加属性声明,就可以在集合中对这些类的对象进行排序。如果我们改变DefaultSort属性,那些声明该属性的类的行为也会跟着改变。我们不必为每个类都改变其算法的代码。
使用声明的方式可以让我们减少重复性的代码。再举GenericComparer的例子,它的优势在于只需要创建一个类就可以通过声明的方式来为每个类型创建行为。关键在于这些类的行为的创建是基于不同的声明,而不是算法的改变。对于任何声明为DefaultSort属性的类型,GenericComparer都会起作用。如果这种排序工作在我们的应用程序中只是出现一两次的话,我们完全可以使用传统的方式来实现。但是如果我们需要用到数十次这种行为时,声明的方式会让我们节省更多的时间,消耗更少的精力。除了声明之外,我们不需要为其写任何代码。
声明式编程是我们的一件有力的武器。它可以让我们尽量避免在编写大量类似算法时产生逻辑错误,创建更简洁易读的代码。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录