C# 经典实例 第一章 类和泛型 #1.17 使用函数对象在列表中执行多种操作

问题:

你希望能够同时对整个对象集合执行多种操作,并在功能上隔离这些操作。

解决方案:

使用函数对象(functor或function object)作为转换集合的工具。函数对象是任何一个可以作为函数被调用的对象。例如,委托

函数、函数指针,甚至是C/C++中定义了operator()的对象。

在软件中,经常需要对一个集合执行多种操作。假定你的股票组合包含了一系列股票。StockPortfolio类包含一个Stock对象的List,并且能够添加股票。

public class StockPortfolio : IEnumerable
{
    List _stocks;

    public StockPortfolio()
    {
        _stocks = new List();
    }

    public void Add(string ticker, double gainLoss)
    {
        _stocks.Add(new Stock()
        {
            Ticker = ticker,
            GainLoss = gainLoss
        });
    }

    public IEnumerable GetWorstPerformers(int topNumber) => _stocks.OrderBy((Stock stock) => stock.GainLoss).Take(topNumber);

    public void SellStocks(IEnumerable stocks)
    {
        foreach (Stock s in stocks)
            _stocks.Remove(s);
    }

    public void PrintPortfolio(string title)
    {
        Console.WriteLine(title);
        _stocks.DisplayStocks();
    }

    #region IEnumerable Members       

    public IEnumerator GetEnumerator() => _stocks.GetEnumerator();

    #endregion

    #region IEnumerable Members   

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

    #endregion 
}

Stock类相当简单。只需要一个股票代码及其利润或亏损百分比。

public class Stock
{
    public double GainLoss { get; set; }
    public string Ticker { get; set; }
}

要使用这个StockPortfolio,可以向其中添加几支带利润/亏损百分比的股票,并输出初始股票组合。有了这个股票组合,你希望得到三支表现最差股票的列表,以便可以通过卖出股票来改进股票组合,然后再次输出它。

StockPortfolio tech = new StockPortfolio() {
    { "OU81", -10.5 },
    { "C#6VR", 2.0 },
    { "PCKD", 12.3 },
    { "BTML", 0.5 },
    { "NOVB", -35.2 },
    { "MGDCD", 15.7 },
    { "GNRCS", 4.0 },
    { "FNCTR", 9.16 },
    { "LMBDA", 9.12 },
    { "PCLS", 6.11 } };

tech.PrintPortfolio("Starting Portfolio");
// 出售表现最差的3支股票    
var worstPerformers = tech.GetWorstPerformers(3);
Console.WriteLine("Selling the worst performers:");
worstPerformers.DisplayStocks();

tech.SellStocks(worstPerformers);
tech.PrintPortfolio("After Selling Worst 3 Performers");

迄今为止,没有发生任何特别令人感兴趣的事情。通过查看GetWorstPerformers方法的内部代码,看一下如何查看三支最差的股票。

 public IEnumerable GetWorstPerformers(int topNumber) => _stocks.OrderBy( (Stock stock) => stock.GainLoss).Take(topNumber);
首先通过调用IEnumerable的OrderBy扩展方法确保列表有序,以便将表现最差的股票列在列表里面。OrderBy方法接受一个lambda表达式,它提供了用于比较的利润/亏损百分比,以找出Take扩展方法中topNumber指示的股票数量。

GetWorstPerformeers返回一个IEnumerable,包含三支表现最差的股票。既然他们没有赚到钱,你应该兑现并卖出他们。对你来说,卖出股票只是简单的在StockPortfolio中从股票列表中删除他们。要实现这一点,可使用另一个函数对象来便利提交给SellStocks函数的股票列表(这里是指表现最差的股票列表),然后从StockPortfolio类维护的内部列表中删除该股票。

public void SellStocks(IEnumerable stocks)
{
    foreach (Stock s in stocks)
        _stocks.Remove(s);
}

讨论:

函数对象具有几种不同的样式:生成器(不带参数的函数)、一元函数(带一个参数的函数)和二元函数(带两个参数的函数)。如果函数对象恰好返回一个布尔值,那么它就有一个更为特定的命名约定:返回布尔值的一元函数称为谓词;返回布尔值的二元函数称为二元谓词。在Framework 中包含了Predicate 和BinaryPredicate 的定义以便于应用这些函数对象。

List 和System.Array 类接受谓词(Predicatec、BinaryPredicate)、动作(Action)、比较(Comparison)和转换(Converter)。这允许以比之前更通用的方式来操作这些集合。

最初以函数对象的方式来思考会有一些挑战,但是一旦花点时间研究它,就会开始看到它带来的强大可能性。任何能够编写一次、调试一次,然后多次使用的代码都是有价值的,函数对象能帮助你达到这一点。

上述示例的输出如下所示。

Starting Portfolio  
(OU81) lost 10.5%  
(C#6VR) gained 2%  
(PCKD) gained 12.3%  
(BTML) gained 0.5%  
(NOVB) lost 35.2%  
(MGDCD) gained 15.7%  
(GNRCS) gained 4%  
(FNCTR) gained 9.16%  
(LMBDA) gained 9.12%  
(PCLS) gained 6.11%  
Selling the worst performers:  
(NOVB) lost 35.2%  
(OU81) lost 10.5%  
(BTML) gained 0.5%  
After Selling Worst 3 Performers  
(C#6VR) gained 2%  
(PCKD) gained 12.3%  
(MGDCD) gained 15.7%  
(GNRCS) gained 4%  
(FNCTR) gained 9.16%  
(LMBDA) gained 9.12%  
(PCLS) gained 6.11% 

参考:

MSDN 文档中的“System.Collections.Generic.List” “System.Linq.Enumerable 类”和“System.Array”主题。

你可能感兴趣的:(C#经典实例,类和泛型)