65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的委托 - 尝试深入了解: 第五部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (18投票s)

2010年11月4日

CPOL

6分钟阅读

viewsIcon

24911

使用委托和泛型类型来改进开发。

插曲

在我的委托系列文章取得适度成功后,我想介绍一些关于如何在代码中利用委托的有用示例。

在本文中,我将委托和泛型数据类型的概念结合起来。如果您不熟悉委托,我鼓励您阅读我的其他文章,链接如下:

问题

如果您是 Web 开发人员,当您想使用缓存来提高网站性能时,会遇到一个典型场景。您还希望能够打开/关闭缓存功能,并且您的过程应立即反映这些更改。通常,这是通过配置(web.config 文件)完成的。通常,当您编写业务对象以获取,例如,某个对象列表时,它会检查缓存是否已启用;如果是,它会检查对象是否在缓存中;如果不在,它会从数据存储中将其放入缓存,然后返回列表。正如您所见,逻辑很简单,但您必须为检索对象列表的每个过程重复它。如果您有数百个可能的过程,您可以想象您必须编写的代码。

解决方案

委托和泛型类型非常适合一起使用,并且使我们的生活更加轻松。事实上,委托已被证明在减少代码冗余方面非常有用。

我的例子

在接下来的示例中,我将介绍两种不同的技术来完成数据缓存:经典方法,以及通过委托和泛型类型。我将提供解释和代码片段。我想澄清一下,这篇文章不是关于缓存的!我只将缓存用作演示委托的示例。缓存本身是一个更大的话题,不是我们文章的主题。

环境

我使用 VS 2010 编写了示例,但只要您的 .NET Framework 版本不低于 3.5,它就可以在 VS 2008 中运行。该项目被创建为一个 C# Web 应用程序。完全没有 UI。没有数据库;我们假设存在一个数据库,并且 DataClass 提供了与数据库的通信。

业务对象

有三个业务对象:ProductProductGroupProductType

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int GroupID { get; set; }
    public double Price { get; set; }
    public int TypeID { get; set; }
    public bool InStock { get; set; }
}


public class ProductType
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ProductGroup
{
    public int ID { get; set; }
    public string Name { get; set; }
}

DataClass 对象

一个特殊的对象代表数据层并与数据库通信。有三个静态函数用于从数据库检索数据。

public class DataClass
{
    public static List<Product> GetProductsByGroupID(int groupID)
    {
        return new List<Product>();
    }

    public static List<Product> 
           GetProductsByGroupIDandAvailability(int groupID,bool inStock)
    {
        return new List<Product>();
    }

    public static List<Product> 
           GetProductsByTypeIDandGroupID(int typeID, int groupID)
    {
        return new List<Product>();
    }
}

正如您所见,没有实际的数据库调用;所有三个函数都只返回新的 List Product 类型对象,以便代码能够编译。这仅出于学习目的。

GlobalUtility

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Caching;


public class GlobalUtility
{
    //usually set via web.config:
    public static int CacheDuration = 30;

    //usually set via web.config:
    public static bool EnableCaching = true;

    public static Cache Cache
    {
        get
        {
            try
            {
                return HttpContext.Current.Cache;
            }
            catch { return new Cache(); }
        }
    }

    public static void CacheData(string key, object data)
    {
        Cache.Insert(key, data, null, 
          DateTime.Now.AddSeconds(CacheDuration), TimeSpan.Zero);
    }

    public static void PurgeCacheItems(string prefix)
    {
        prefix = prefix.ToLower();
        List<string> itemsToRemove = new List<string>();
        try
        {
            IDictionaryEnumerator enumerator = Cache.GetEnumerator();
            while (enumerator.MoveNext())
            {

                if (enumerator.Key.ToString().ToLower().StartsWith(prefix))
                    itemsToRemove.Add(enumerator.Key.ToString());
            }
            foreach (string itemToRemove in itemsToRemove)
                Cache.Remove(itemToRemove);
        }
        catch { }
    }     
}

这个实用程序类包含一组用于处理缓存的静态过程。一切都已简化。CacheDurationEnableCaching 变量只是设置为一些值。在实际生活中,这些值将从应用程序配置文件中获取。Cache 过程返回 Cache 对象。CacheData 缓存一个对象,并将其与一个键关联。PurgeCacheItems 过程从缓存中删除所有键以指定键前缀开头的所有对象。

Product 类的静态过程

现在,让我们在 Product 类中添加一些静态过程。

#region Static Functions

public static List<Product> GetByGroupID(int groupID)
{
    string key =  "Product_GetByGroupID_" + groupID.ToString();

    List<Product> dataObject;
    if (GlobalUtility.EnableCaching && null != GlobalUtility.Cache[key])
        dataObject = (List<Product>)GlobalUtility.Cache[key];
    else
    {
        dataObject = DataClass.GetProductsByGroupID(groupID);
        if (GlobalUtility.EnableCaching) GlobalUtility.CacheData(key, dataObject);
    }
    return dataObject;
}

public static List<Product> GetByGroupIDandAvailability(int groupID, bool inStock)
{
    string key = "Product_GetByGroupIDandAvailability_" + 
                 groupID.ToString() + "_" + inStock.ToString();
    List<Product> dataObject;
    if (GlobalUtility.EnableCaching && null != GlobalUtility.Cache[key])
        dataObject = (List<Product>)GlobalUtility.Cache[key];
    else
    {
        dataObject = DataClass.GetProductsByGroupIDandAvailability(groupID, inStock);
        if (GlobalUtility.EnableCaching) GlobalUtility.CacheData(key, dataObject);
    }
    return dataObject;
}

public static List<Product> GetByTypeIDandGroupID(int groupID, int typeID)
{
    string key = "Product_GetByTypeIDandGroupID_" + 
                 typeID.ToString() + "_" + groupID.ToString();

    List<Product> dataObject;
    if (GlobalUtility.EnableCaching && null != GlobalUtility.Cache[key])
        dataObject = (List<Product>)GlobalUtility.Cache[key];
    else
    {
        dataObject = DataClass.GetProductsByTypeIDandGroupID(groupID, typeID);
        if (GlobalUtility.EnableCaching) GlobalUtility.CacheData(key, dataObject);
    }
    return dataObject;
}

#endregion

如果启用了缓存,它们会在缓存中查找结果;否则,它们会使用 DataClass 对象获取结果。

但是,请想象一下,您不仅有三个具有缓存启用功能的函数,还有大约一百个类似的更多过程。对于其中的每一个,您都必须重复类似的​​代码。这是一项艰巨的任务!!!而且还有更多——如果您想更改缓存块中的某些代码怎么办?然后您必须在每个函数中更改它!一点也不令人愉快……

一些思考

让我们看一下这三个函数。首先,我们将寻找相似之处。

  1. 结构上它们非常相似。
  2. 操作的逻辑顺序是相同的。
  3. 键的使用是相同的。
  4. 它们都返回值

现在,有什么不同?

  1. 类似的函数可能返回不同的数据类型。
  2. 输入参数的数量和数据类型不同。
  3. 内部调用的 DataClass 函数不同。

如果它们在结构和逻辑上相似,就应该考虑使用委托来减少冗余。如果参数数据类型和返回值数据类型未知,那么想法就是利用泛型数据类型。如果所有函数都返回值,我们可以尝试使用 Func 委托。如果参数数量不同,这意味着我们将不得不使用重载方法。

带 Func 的泛型函数

GlobalUtility 类中,我们添加了一个新函数。

public static TOutput GetCashedDataObject<T1,TOutput>(string key, 
       T1 t1, Func<T1,TOutput> func)
{
    string key = key + t1.ToString();
    TOutput dataObject;
    if (GlobalUtility.EnableCaching && null != Cache[key])
        dataObject = (TOutput)Cache[key];
    else
    {
        dataObject = func(t1);
        if (GlobalUtility.EnableCaching) CacheData(key, dataObject);
    }

    return dataObject;
}

让我们把它切开,像在显微镜下一样观察它。

Toutput GetCashedDataObject<T1,TOutput>

当您在尖括号中放置一些奇怪的 <T1,TOutput> 时,您实际上是在向编译器解释这些是您想使用的数据类型。对于所有声明了泛型数据类型的函数,最后一个参数是返回值的数据类型。当您从代码中调用函数时,您会指定将使用哪些具体数据类型。此函数接受一些参数。

(string key, T1 t1, Func<T1,TOutput> func)
  1. 键只是一个字符串。
  2. t1T1 数据类型的参数。
  3. func 是 Func 类型的委托。Func 类型委托由环境创建,它们也是委托的泛型数据类型。实际上,这里的意思是第三个参数是一个函数,它接受一个 T1 类型的参数并返回一个 Toutput 类型的值。请在脑海中记住,此函数仅接受一个参数。

最后是函数体。

{
    string key = key + t1.ToString();
    TOutput dataObject;
    if (GlobalUtility.EnableCaching && null != Cache[key])
        dataObject = (TOutput)Cache[key];
    else
    {
        dataObject = func(t1);
        if (GlobalUtility.EnableCaching) CacheData(key, dataObject);
    }

    return dataObject;
}

正如您所见,这与上一个示例中的功能完全相同,但我们使用了

dataObject = DataClass.GetProductsByGroupID(groupID);

代替

dataObject = func(t1);

还记得脑海中的笔记吗?func 函数可以表示任何只接受一个参数并返回值的功能。现在我们将尝试更改现有函数。

public static List<Product> GetByGroupID(int groupID)
{
    string key =  "Product_GetByGroupID_" + groupID.ToString();

    List<Product> dataObject;
    if (GlobalUtility.EnableCaching && null != GlobalUtility.Cache[key])
        dataObject = (List<Product>)GlobalUtility.Cache[key];
    else
    {
        dataObject = DataClass.GetProductsByGroupID(groupID);
        if (GlobalUtility.EnableCaching) GlobalUtility.CacheData(key, dataObject);
    }
    return dataObject;
}

使用新的函数。

public static List<Product> GetByGroupID(int groupID)
{
    string keyPrefix = "Product_GetByGroupID_";

    return GlobalUtility.GetCashedDataObject<int,List<Product>>(
                         keyPrefix, groupID, grID =>
    {
        return DataClass.GetProductsByGroupID(grID);
    });
}

正如您所看到的,我们创建了一个键字符串并调用了 GlobalUtility.GetCashedDataObject<int,List<Product>> 函数。第一个参数是键前缀。第二个参数是组 ID。第三个参数是我们内联定义的委托,通过调用数据类函数。在这种情况下,我们使用 Lambda 语法。

grID =>
{
  return DataClass.GetProductsByGroupID(grID);
});

我们可以将泛型数据类型用于任何数据类型。唯一的限制是输入参数的数量。它只能有一个。如果我们想处理多个参数,我们需要重载 GetCashedDataObject 函数。对于两个参数,它将是。

public static TOutput GetCashedDataObject<T1,T2,TOutput>(string keyPrefix, 
                                   T1 t1, T2 t2, Func<T1,T2,TOutput> func)
{
    string key = keyPrefix + t1.ToString() + "_" + t2.ToString();
    TOutput dataObject;
    if (GlobalUtility.EnableCaching && null != Cache[key])
        dataObject = (TOutput)Cache[key];
    else
    {
        dataObject = func(t1, t2);
        if (GlobalUtility.EnableCaching) CacheData(key, dataObject);
    }

    return dataObject;
}

您看到了这个模式,对吧?它非常简单。现在我们可以替换其他两个函数。它们接受两个参数;即使输入参数的数据类型不同,也没关系。所以 Product 类的静态函数的所有部分现在是。

public static List<Product> GetByGroupID(int groupID)
{
    string keyPrefix = "Product_GetByGroupID_";

    return GlobalUtility.GetCashedDataObject<int,List<Product>>(
           keyPrefix, groupID, grID =>
    {
       return DataClass.GetProductsByGroupID(grID);
    });

}

public static List<Product> GetByGroupIDandAvailability(int groupID, bool inStock)
{
    string keyPrefix = "Product_GetByGroupIDandAvailability_";

    return GlobalUtility.GetCashedDataObject<int,bool, 
           List<Product>>(keyPrefix, groupID, inStock,(grID,inst) =>
    {
        return DataClass.GetProductsByGroupIDandAvailability(grID, inst);

    });
}

public static List<Product> GetByTypeIDandGroupID(int groupID, int typeID)
{
    string keyPrefix = "Product_GetByGroupIDandAvailability_";

    return GlobalUtility.GetCashedDataObject<int,int,
           List<Product>>(keyPrefix, groupID, typeID, (grID, tpID) =>
    {
        return DataClass.GetProductsByTypeIDandGroupID(grID, tpID);

    });
}

区别很大!如果您想更改缓存的功能,只需在一个地方完成即可。

结论

这是委托的众多用途之一,以及如何在开发中利用泛型数据类型。它有助于消除冗余代码,并使您的程序更面向对象且更具可伸缩性。如果您喜欢这篇文章,请投票支持。这对我了解我的文章是否受欢迎非常有帮助。

© . All rights reserved.