c#高级编程(第6版)--第九章-第3节
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
accounts。Add(new Account(〃Christian〃; 1500));
accounts。Add(new Account(〃Sharon〃; 2200));
accounts。Add(new Account(〃Katie〃; 1800));
累加所有Account对象的传统方式是用foreach语句迭代所有的Account对象,如下所示。foreach语句使用IEnumerable接口迭代集合的元素,所以AccumulateSimple()方法的参数是IEnumerable类型。这样,AccumulateSimple()方法就可以用于所有实现IEnumerable接口的集合类。在这个方法的实现代码中,直接访问Account对象的Balance属性:
public static class Algorithm
{
public static decimal AccumulateSimple(IEnumerable e)
{
decimal sum = 0;
foreach (Account a in e)
{
sum += a。Balance;
}
return sum;
}
}
Accumulate()方法的调用方式如下:
decimal amount = Algorithm。AccumulateSimple(accounts);
第一个实现代码的问题是,它只能用于Account对象。使用泛型方法就可以避免这个问题。
Accumulate()方法的第二个版本接受实现了IAccount接口的任意类型。如前面的泛型类所述,泛型类型可以用where子句来限制。这个子句也可以用于泛型方法。Accumulate()方法的参数改为IEnumerable。IEnumerable是IEnumerable接口的泛型版本,由泛型集合类实现。
public static decimal Accumulate(IEnumerable coll)
where TAccount : IAccount
{
decimal sum = 0;
foreach (TAccount a in coll)
{
sum += a。Balance;
}
return sum;
}
Account类现在重构为执行接口IAccount:
public class Account : IAccount
{
//。。。
IAccount接口定义了只读属性Balance和Name:
public interface IAccount
{
decimal Balance { get; }
string Name { get; }
}
将Account类型定义为泛型类型参数,就可以调用新的Accumulate()方法:
decimal amount = Algorithm。Accumulate(accounts);
因为编译器会从方法的参数类型中自动推断出泛型类型参数,所以以如下方式调用Accumulate()方法是有效的:
decimal amount = Algorithm。Accumulate(accounts);
泛型类型实现IAccount接口的要求过于严厉。这个要求可以使用泛型委托来改变。在下一节中,Accumulate()方法将改为独立于任何接口。
9。6 泛型委托
如第7章所述,委托是类型安全的方法引用。通过泛型委托,委托的参数可以在以后定义。
Framework定义了一个泛型委托EventHandler,它的第二个参数是TEventArgs类型,所以不再需要为每个新参数类型定义新委托了。
public sealed delegate void EventHandler(object sender; TEventArgs e)
where TEventArgs : EventArgs
9。6。1 执行委托调用的方法
把Accumulate()方法改为有两个泛型类型。TInput是要累加的对象类型,TSummary是返回类型。Accumulate的第一个参数是IEnumerable接口,这与以前相同。第二个参数需要Action委托引用一个方法,来累加所有的结余。
在实现代码中,现在给每个元素调用Action委托引用的方法,再返回计算的总和:
public delegate TSummary Action(TInput t; TSummary u);
public static TSummary Accumulate(IEnumerable coll;
Action action)
{
TSummary sum = default(TSummary);
foreach (TInput input in coll)
{
sum = action(input; sum);
}
return sum;
}
Accumulate方法可以通过匿名方法调用,该匿名方法指定,账目的结余应累加到第二个Action类型的参数中:
decimal amount = Algorithm。Accumulate(
accounts;
delegate(Account a; decimal d)
{ return a。Balance + d; });
除了使用匿名方法之外,还可以使用l表达式把它传送给第二个参数:
decimal amount = Algorithm。Accumulate 《 Account; decimal 》 (
accounts; (a; d) = 》 a。Balance + d;);
提示:
匿名方法和l表达式参见第7章。
如果Account结余的累加需要进行多次,就可以把该功能放在一个AccountAdder()方法中:
static decimal AccountAdder(Account a; decimal d)
{
return a。Balance + d;
}
联合使用AccountAdder方法和Accumulate方法:
decimal amount = Algorithm。Accumulate(
accounts; AccountAdder);
Action委托引用的方法可以实现任何逻辑。例如,可以进行乘法操作,而不是加法操作。
Accumulate()方法和AccumulateIf()方法一起使用,会更灵活。在AccumulateIf()中,使用了另一个Predicate类型的参数。Predicate委托引用的方法会检查某个账目是否应累加进去。在foreach语句中,只有谓词match返回true,才会调用action方法:
public static TSummary AccumulateIf(
IEnumerable coll;
Action action;
Predicate match)
{
TSummary sum = default(TSummary);
foreach (TInput a in coll)
{
if (match(a))
{
sum = action(a; sum);
}
}
return sum;
}
调用AccumulateIf()方法可以实现累加和执行谓词。这里按照第二个l表达式的定义a = 》 a。Balance 》 2000,只累加结余大于2000的账目:
decimal amount = Algorithm。AccumulateIf 《 Account; decimal 》 (
accounts; (a; d) = 》 a。Balance + d; a = 》 a。Balance 》 2000);
9。6。2 对Array类使用泛型委托
第5章使用Iparable和Iparer接口,演示了Array类的几个排序技术。从 2。0开始,Array类的一些方法把泛型委托类型用作参数。表9…2列出了这些方法、泛型类型和功能。
表 9…2
方 法
泛型参数类型
说 明
Sort()
int parison(T x; T y)
Sort()方法定义了几个重载版本。其中一个重载版本需要一个parison类型的参数。Sort()使用委托引用的方法对集合中的所有元素排序
ForEach()
void Action(T obj)
ForEach()方法对集合中的每一项调用由Action委托引用的方法
FindAll()
Find()
FindLast()
FindIndex()
FindLastIndex()
bool Predicate(T match)
FindXXX()方法将Predicate委托作为其参数。由委托引用的方法会调用多次,并一个接一个地传送集合中的元素。Find()方法在谓词第一次返回true时停止搜索,并返回这个元素。FindIndex()返回查找到的第一个元素的索引。FindLast()和FindLastIndex()以逆序方式对集合中的元素调用谓词,因此返回最后一项或最后一个索引。FindAll()返回一个新列表,其中包含谓词为true的所有项
ConvertAll()
TOutput Converter(TInput input)
ConvertAll()方法给集合中的每个元素调用Converter《 TInput; TOutput》委托,返回一列转换好的元素
TrueForAll()
bool Predicate(T match)
TrueForAll()方法给每个元素调用谓词委托。如果谓词给每个元素都返回true,则TrueForAll()也返回true。如果谓词为一个元素返回了false,TrueForAll()就返回false
下面看看如何使用这些方法。
Sort()方法把这个委托作为参数:
public delegate int parison(T x; T y);
这样,就可以使用l表达式传送两个Person对象,给数组排序。对于Person对象数组,参数T是Person类型:
Person'' persons = {
new Person(〃Emerson〃; 〃Fittipaldi〃);
new Person(〃Niki〃; 〃Lauda〃);
new Person(〃Ayrton〃; 〃Senna〃);
new Person(〃Michael〃; 〃Schumacher〃)
};
Array。Sort(persons; (p1; p2)=》 p1。FirstnamepareTo(p2。Firstname));
Array。ForEach()方法将Action委托作为参数,给数组的每个元素执行操作:
public delegate void Action(T obj);
于是,就可以传送Console。WriteLine方法的地址,将每个人写入控制台。WriteLine()方法的一个重载版本将Object类作为参数类型。由于Person派生于Object,所以它适合于Person数组:
Array。ForEach(persons; Console。WriteLine);
ForEach()语句的结果将persons变量引用的集合中的每个人都写入控制台:
Emerson Fittipaldi
Niki Lauda
Ayrton Senna
Michael Schumacher
如果需要更多的控制,则可以传送一个l表达式,其参数应匹配委托定义的参数:
Array。ForEach(persons; p=》 Console。WriteLine(〃{0}〃; p。Lastname));
下面是写入控制台的姓氏:
Fittipaldi
Lauda
Senna
Schumacher
Array。FindAll()方法需要Predicate委托:
public delegate bool Predicate(T match);
Array。FindAll()方法为数组中的每个元素调用谓词,并返回一个谓词是true的数组。在这个例子中,对于Lastname以字符串〃S〃开头的所有Person对象,都返回true。
Person'' sPersons = Array。FindAll(persons; p=》 p。Lastname。StartsWith(〃S〃);
迭代返回的集合sPersons,并写入控制台,结果如下:
Ayrton Senna
Michael Schumacher
ArraynvertAll()方法使用泛型委托Converter和两个泛型类型。第一个泛型类型TInput是输入参数,第二个泛型类型TOutput是返回类型。
public delegate TOutput Converter(TInput input);
如果一种类型的数组应转换为另一种类型的数组,就可以使用ConvertAll()方法。下面是一个与Person类无关的Racer类。Person类有Firstname和Lastname属性,而Racer类为赛手的姓名定义了一个属性Name:
public class Racer
{
public Racer(string name)
{
this。name = name;
}
public string Name {get; set}
public string Team {get; set}
}
使用ArraynvertAll(),很容易将persons数组转换为Racer数组。给每个Person元素调用委托。在每个Person元素的匿名方法的执行代码中,创建了一个新的Racer对象,将firstname和lastname连接起来传送给带一个字符串参数的构造函数。结果是一个Racer对象数组:
Racer'' racers =
ArraynvertAll(
persons; p = 》
new Racer(String。Format(〃{0} {1}〃; p。FirstName; p。LastName));
9。7 Framework的其他泛型类型
除了Systemllections。Generic命名空间之外, Framework还有其他泛型类型。这里讨论的结构和委托都位于System命名空间中,用于不同的目的。
本节讨论如下内容:
● 结构Nullable
● 委托EventHandler
● 结构ArraySegment
9。7。1 结构Nullable
数据库中的数字和编程语言中的数字有显著不同的特征,因为数据库中的数字可以为空,C#中的数字不能为空。Int32是一个结构,而结构实现为值类型,所以它不能为空。
只有在数据库中,而且把XML数据映射�