C# 中的委托 - 尝试深入了解: 第五部分
使用委托和泛型类型来改进开发。
插曲
在我的委托系列文章取得适度成功后,我想介绍一些关于如何在代码中利用委托的有用示例。
在本文中,我将委托和泛型数据类型的概念结合起来。如果您不熟悉委托,我鼓励您阅读我的其他文章,链接如下:
问题
如果您是 Web 开发人员,当您想使用缓存来提高网站性能时,会遇到一个典型场景。您还希望能够打开/关闭缓存功能,并且您的过程应立即反映这些更改。通常,这是通过配置(web.config 文件)完成的。通常,当您编写业务对象以获取,例如,某个对象列表时,它会检查缓存是否已启用;如果是,它会检查对象是否在缓存中;如果不在,它会从数据存储中将其放入缓存,然后返回列表。正如您所见,逻辑很简单,但您必须为检索对象列表的每个过程重复它。如果您有数百个可能的过程,您可以想象您必须编写的代码。
解决方案
委托和泛型类型非常适合一起使用,并且使我们的生活更加轻松。事实上,委托已被证明在减少代码冗余方面非常有用。
我的例子
在接下来的示例中,我将介绍两种不同的技术来完成数据缓存:经典方法,以及通过委托和泛型类型。我将提供解释和代码片段。我想澄清一下,这篇文章不是关于缓存的!我只将缓存用作演示委托的示例。缓存本身是一个更大的话题,不是我们文章的主题。
环境
我使用 VS 2010 编写了示例,但只要您的 .NET Framework 版本不低于 3.5,它就可以在 VS 2008 中运行。该项目被创建为一个 C# Web 应用程序。完全没有 UI。没有数据库;我们假设存在一个数据库,并且 DataClass
提供了与数据库的通信。
业务对象
有三个业务对象:Product
、ProductGroup
和 ProductType
。
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 { }
}
}
这个实用程序类包含一组用于处理缓存的静态过程。一切都已简化。CacheDuration
和 EnableCaching
变量只是设置为一些值。在实际生活中,这些值将从应用程序配置文件中获取。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
对象获取结果。
但是,请想象一下,您不仅有三个具有缓存启用功能的函数,还有大约一百个类似的更多过程。对于其中的每一个,您都必须重复类似的代码。这是一项艰巨的任务!!!而且还有更多——如果您想更改缓存块中的某些代码怎么办?然后您必须在每个函数中更改它!一点也不令人愉快……
一些思考
让我们看一下这三个函数。首先,我们将寻找相似之处。
- 结构上它们非常相似。
- 操作的逻辑顺序是相同的。
- 键的使用是相同的。
- 它们都返回值。
现在,有什么不同?
- 类似的函数可能返回不同的数据类型。
- 输入参数的数量和数据类型不同。
- 内部调用的
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)
- 键只是一个字符串。
t1
是T1
数据类型的参数。- 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);
});
}
区别很大!如果您想更改缓存的功能,只需在一个地方完成即可。
结论
这是委托的众多用途之一,以及如何在开发中利用泛型数据类型。它有助于消除冗余代码,并使您的程序更面向对象且更具可伸缩性。如果您喜欢这篇文章,请投票支持。这对我了解我的文章是否受欢迎非常有帮助。