C# 通用的、编译时安全的延迟加载方法

C# 通用的、编译时安全的延迟加载方法,c#,generics,lazy-loading,C#,Generics,Lazy Loading,假设我创建了一个包装器类,如下所示: public class Foo : IFoo { private readonly IFoo innerFoo; public Foo(IFoo innerFoo) { this.innerFoo = innerFoo; } public int? Bar { get; set; } public int? Baz { get; set; } } 这里的想法是,innerFoo可能包装数

假设我创建了一个包装器类,如下所示:

public class Foo : IFoo
{
    private readonly IFoo innerFoo;

    public Foo(IFoo innerFoo)
    {
        this.innerFoo = innerFoo;
    }

    public int? Bar { get; set; }
    public int? Baz { get; set; }
}
这里的想法是,
innerFoo
可能包装数据访问方法或类似昂贵的东西,我只希望它的
GetBar
GetBaz
方法被调用一次。所以我想在它周围创建另一个包装器,它将保存第一次运行时获得的值

当然,这样做很简单:

int IFoo.GetBar()
{
    if ((Bar == null) && (innerFoo != null))
        Bar = innerFoo.GetBar();
    return Bar ?? 0;
}

int IFoo.GetBaz()
{
    if ((Baz == null) && (innerFoo != null))
        Baz = innerFoo.GetBaz();
    return Baz ?? 0;
}
但是如果我用10个不同的属性和30个不同的包装来做这件事,它会变得非常重复。所以我想,嘿,让我们做个普通的:

T LazyLoad<T>(ref T prop, Func<IFoo, T> loader)
{
    if ((prop == null) && (innerFoo != null))
        prop = loader(innerFoo);
    return prop;
}
相反,我必须更改
Bar
以具有显式的支持字段,并编写显式的getter和setter。这很好,除了我最终编写的冗余代码比我最初编写的还要多

然后我考虑了使用表达式树的可能性:

T LazyLoad<T>(Expression<Func<T>> propExpr, Func<IFoo, T> loader)
{
    var memberExpression = propExpr.Body as MemberExpression;
    if (memberExpression != null)
    {
        // Use Reflection to inspect/set the property
    }
}
但它实际上并不安全,因为一些不太聪明的人(即我自己,从现在起3天内,我不可避免地忘记了这是如何在内部实现的)可能会决定写以下内容:

return LazyLoad(f => 3, f => f.GetBar());
这将导致崩溃或导致意外/未定义的行为,具体取决于我编写
LazyLoad
方法的防御方式。所以我也不太喜欢这种方法,因为它可能导致运行时错误,而这种错误在第一次尝试时就可以避免。它还依赖于反射,在这里感觉有点脏,尽管这段代码对性能不敏感

现在我也可以决定全力以赴,使用DynamicProxy进行方法拦截,而不必编写任何代码,事实上,我已经在一些应用程序中这样做了。但这段代码驻留在许多其他程序集所依赖的核心库中,以如此低的级别引入这种复杂性似乎是错误的。将基于拦截器的实现与
IFoo
接口分离,并将其放入自己的程序集中,这并没有真正的帮助;事实上,这个类仍然会被广泛使用,必须被使用,所以这不是一个可以用一点DI魔法简单解决的问题

我已经想到的最后一个选择是采用如下方法:

 T LazyLoad<T>(Func<T> getter, Action<T> setter, Func<IFoo, T> loader) { ... }
T懒散加载(Func getter、Action setter、Func loader){…}
这个选项也是非常“meh”的——它避免了反射,但仍然容易出错,而且它并没有真正减少那么多重复。这几乎和必须为每个属性编写显式getter和setter一样糟糕

也许我只是在吹毛求疵,但这个应用程序仍处于早期阶段,随着时间的推移,它将大幅增长,我真的希望保持代码整洁

一句话:我陷入了僵局,正在寻找其他想法

问题: 有没有办法清理顶部的延迟加载代码,以便实现:

  • 保证编译时的安全性,如
    ref
    版本
  • 实际上减少代码重复的数量,比如
    表达式
    版本;及
  • 不承担任何重要的附加依赖项

换句话说,有没有一种方法可以通过使用常规的C#语言特性和一些可能的小助手类来实现这一点?还是我必须接受这里有一个折衷方案,并从列表中删除上述要求之一?

如果您可以使用.NET 4,您应该使用

它提供了您需要的功能,并且是完全线程安全的


如果您不能使用.NET4,我仍然建议您查看它,并“窃取”它的设计和API。它使延迟实例化变得非常容易。

如果您所要做的只是避免将此代码复制300次:

private int? bar;
public int Bar
{
    get
    {
        if (bar == null && innerFoo != null)
           bar = innerFoo.GetBar();
        return bar ?? 0;           
    }
    set
    {
        bar = value;
    }
}
然后你可以创建一个索引器

enum FooProperties
{
    Bar,
    Baz,
}

object[] properties = new object[2];

public object this[FooProperties property]
{
    get
    {
        if (properties[property] == null)
        {
           properties[property] = GetProperty(property);
        }
        return properties[property];           
    }
    set
    {
        properties[property] = value;
    }
}

private object GetProperty(FooProperties property)
{
    switch (property)
    {
         case FooProperties.Bar:
              if (innerFoo != null)
                  return innerFoo.GetBar();
              else
                  return (int)0;

         case FooProperties.Baz:
              if (innerFoo != null)
                  return innerFoo.GetBaz();
              else
                  return (int)0;

         default:
              throw new ArgumentOutOfRangeException();
    }
}
这需要在读取值时强制转换该值:

int myBar = (int)myFoo[FooProperties.Bar];
但它避免了大多数其他问题

编辑以添加:

好的,这是你应该做的,但是不要告诉任何人你做了,或者我建议了。选择此选项:

int IFoo.GetBar()
{
    return LazyLoad(ref Bar, f => f.GetBar());  // <--- Won't compile
}
int-IFoo.GetBar()
{

return LazyLoad(ref Bar,f=>f.GetBar());//我最终实现了一个类似于.NET 4中的
Lazy
类的东西,但更适合于“缓存”的特定概念,而不是“延迟加载”

准惰性类如下所示:

public class CachedValue<T>
{
    private Func<T> initializer;
    private bool isValueCreated;
    private T value;

    public CachedValue(Func<T> initializer)
    {
        if (initializer == null)
            throw new ArgumentNullException("initializer");
        this.initializer = initializer;
    }

    public CachedValue(T value)
    {
        this.value = value;
        this.isValueCreated = true;
    }

    public static implicit operator T(CachedValue<T> lazy)
    {
        return (lazy != null) ? lazy.Value : default(T);
    }

    public static implicit operator CachedValue<T>(T value)
    {
        return new CachedValue<T>(value);
    }

    public bool IsValueCreated
    {
        get { return isValueCreated; }
    }

    public T Value
    {
        get
        {
            if (!isValueCreated)
            {
                value = initializer();
                isValueCreated = true;
            }
            return value;
        }
    }
}
最终,我得到的是一个几乎是POCO的类,使用构造函数中的延迟加载程序初始化的自动属性(从
deferred
合并为null):

我现在坚持这一点。使用这种方法似乎很难搞乱包装器类。由于使用了隐式转换运算符,它甚至可以安全地抵抗
null
实例


它仍然有点黑客的感觉,我仍然愿意接受更好的想法。

我确实研究过这个类(仍然在.NET 3.5上,但我可以自己实现)-但我无法确定如何将其应用到设计中,请记住,
innerFoo
实际上可能是
null
,并且外部类应该能够更改属性值(因此是公共setter)。也许我只是没有看到树的森林-你可以举一个例子,说明使用
Lazy
,这会是什么样子吗?你真的需要一个公共可读写属性,可以填充也可以不填充,以及一个公共GetX()设置属性并返回值?@Jeffrey:长话短说,是的,因为这个类的部分原理是,属性可能会被急切地加载。例如,如果对象包装了几个数据库调用,则更高级别的组件可能会决定从一个大记录集中加载其中几个,以避免重复的
int IFoo.GetBar()
{
    return LazyLoad(ref Bar, f => f.GetBar());  // <--- Won't compile
}
public class CachedValue<T>
{
    private Func<T> initializer;
    private bool isValueCreated;
    private T value;

    public CachedValue(Func<T> initializer)
    {
        if (initializer == null)
            throw new ArgumentNullException("initializer");
        this.initializer = initializer;
    }

    public CachedValue(T value)
    {
        this.value = value;
        this.isValueCreated = true;
    }

    public static implicit operator T(CachedValue<T> lazy)
    {
        return (lazy != null) ? lazy.Value : default(T);
    }

    public static implicit operator CachedValue<T>(T value)
    {
        return new CachedValue<T>(value);
    }

    public bool IsValueCreated
    {
        get { return isValueCreated; }
    }

    public T Value
    {
        get
        {
            if (!isValueCreated)
            {
                value = initializer();
                isValueCreated = true;
            }
            return value;
        }
    }
}
public static class Deferred
{
    public static CachedValue<T> From<TSource, T>(TSource source, 
        Func<TSource, T> selector)
    {
        Func<T> initializer = () =>
            (source != null) ? selector(source) : default(T);
        return new CachedValue<T>(initializer);
    }
}
public class CachedFoo : IFoo
{
    public CachedFoo(IFoo innerFoo)
    {
        Bar = Deferred.From(innerFoo, f => f.GetBar());
        Baz = Deferred.From(innerFoo, f => f.GetBaz());
    }

    int IFoo.GetBar()
    {
        return Bar;
    }

    int IFoo.GetBaz()
    {
        return Baz;
    }

    public CachedValue<int> Bar { get; set; }
    public CachedValue<int> Baz { get; set; }
}
CachedFoo foo = new CachedFoo(myFoo);
foo.Bar = 42;
foo.Baz = 86;