通过 lambda 工厂创建对象与直接“new Type()”语法

本文关键字:Type new 语法 工厂 lambda 创建对象 通过 | 更新日期: 2023-09-27 18:34:26

例如,考虑一个实用程序类SerializableList

public class SerializableList : List<ISerializable>
{
    public T Add<T>(T item) where T : ISerializable
    {
        base.Add(item);
        return item;
    }
    public T Add<T>(Func<T> factory) where T : ISerializable
    {
        var item = factory();
        base.Add(item);
        return item;
    }
}

通常我会这样使用它:

var serializableList = new SerializableList(); 
var item1 = serializableList.Add(new Class1());
var item2 = serializableList.Add(new Class2());

我也可以通过保理来使用它,如下所示:

var serializableList = new SerializableList(); 
var item1 = serializableList.Add(() => new Class1());
var item2 = serializableList.Add(() => new Class2());

第二种方法似乎是首选的使用模式,正如我最近在SO上注意到的那样。真的是这样(为什么,如果是的话(还是只是品味问题?

通过 lambda 工厂创建对象与直接“new Type()”语法

鉴于您的示例,工厂方法很愚蠢。除非被调用方需要能够控制实例化点、实例化多个实例或延迟计算,否则它只是无用的开销。

编译器将无法优化委托创建。

引用您在问题注释中给出的工厂语法的示例。这两个示例都试图(尽管效果很差(提供有保证的实例清理。

如果考虑使用 using 语句:

using (var x = new Something()) { }

朴素的实现将是:

var x = new Something();
try 
{ 
}
finally
{
   if ((x != null) && (x is IDisposable))
     ((IDisposable)x).Dispose();
}

此代码的问题在于,在分配x之后,但在输入try块之前,可能会发生异常。如果发生这种情况,x将无法正确释放,因为finally块将不会执行。为了解决这个问题,using语句的代码实际上更像是:

Something x = null;
try 
{
   x = new Something();
}
finally
{
   if ((x != null) && (x is IDisposable))
      ((IDisposable)x).Dispose();
}

使用工厂参数引用的两个示例都在尝试处理同一问题。传递工厂允许在受保护的块中实例化实例。直接传递实例可能会出错,而不会调用Dispose()

在这些情况下,传递工厂参数是有意义的。

缓存

在您提供的示例中,正如其他人指出的那样,它没有意义。相反,我会给你另一个例子,

public class MyClass{
    public MyClass(string file){
        // load a huge file
        // do lots of computing...
        // then store results...
    }
}
private ConcurrentDictionary<string,MyClass> Cache = new ....
public MyClass GetCachedItem(string key){
    return Cache.GetOrAdd(key, k => new MyClass(key));
}

在上面的例子中,假设我们正在加载一个大文件,我们正在计算一些东西,我们对该计算的最终结果感兴趣。为了加快我的访问速度,当我尝试通过缓存加载文件时,缓存会向我返回缓存条目(如果有的话(,只有当缓存找不到该项目时,它才会调用 Factory 方法,并创建新的 MyClass 实例。

因此,您多次读取文件,但您只创建仅保存一次数据的类实例。此模式仅对缓存目的有用。

但是,如果您没有缓存,并且每次迭代都需要调用 new 运算符,那么使用工厂模式根本没有意义。

备用错误对象或错误日志记录

出于某种原因,如果创建失败,List 可以创建一个错误对象,例如,

 T defaultObject = ....
public T Add<T>(Func<T> factory) where T : ISerializable
{
    T item;
    try{
        item = factory();
    }catch(ex){
        Log(ex);
        item = defaultObject;
    }
    base.Add(item);
    return item;
}

在此示例中,您可以监视工厂是否在创建新对象时生成异常,当发生这种情况时,您记录错误,并返回其他内容并在列表中保留一些默认值。我不知道它的实际用途是什么,但错误日志记录在这里听起来更好。

不,没有传递工厂而不是值的一般偏好。但是,在非常特殊的情况下,您将更愿意传递工厂方法而不是值。

想想吧:

将参数作为值传递或 将其作为工厂方法传递(例如使用 Func<T> (?

答案很简单:执行顺序

  • 在第一种情况下,您需要传递值,因此必须在调用目标方法之前获取它。
  • 在第二种情况下,您可以推迟值创建/计算/获取,直到目标方法需要它。

为什么要推迟价值的创造/计算/获取? 显而易见的事情浮现在脑海中:

  • 处理器密集型或内存密集型价值的创建,您希望仅在确实需要该值时才发生(按需(。这就是延迟加载
  • 如果值创建依赖于目标方法可访问但不能从目标方法外部访问的参数。因此,您将传递Func<T, T>而不是Func<T>

该问题比较了具有不同目的的方法。第二个应该命名为 创建并添加<T>(Func<T> factory) .

因此,根据所需的功能,应使用一种或另一种方法。