延迟加载 C# 自动实现的属性

Lazy loading for C# auto-implemented properties

我正在尝试做三件事:

如何延迟加载 C# 自动属性?

要完成以上两件事,我想我需要使用 Reflection(或者其他)。到目前为止,这是我尝试过的。我有一个对象测试:

public class MyClass
{
    private Lazy<MyObjectClass> lazyObjectClass = new Lazy<MyObjectClass>(() => new MyObjectClass());
    public MyObjectClass MyObject { get { return lazyObjectClass.Value; } }
}

通常,MyObjectClass 会在 MyObject 调用时初始化。

但是,我想自动初始化 MyObjectClass。所以,我将 MyClass 更改为这样:

public class MyClass
{
    public MyClass()
    {
        //Get field
        var field = this.GetType().GetField("lazyObjectClass", BindingFlags.NonPublic | BindingFlags.Instance);
        //Get value of field
        Lazy<MyObjectClass> lazyValue = (Lazy<MyObjectClass>)field.GetValue(this);

        //Get property MyObject
        var property = this.GetType().GetProperty("MyObject");
        //Set value to the MyObject
        property.SetValue(this, lazyValue.Value); // <- when It called, MyObjectClass is ititialize too
    }
    private Lazy<MyObjectClass> lazyObjectClass = new Lazy<MyObjectClass>(() => new MyObjectClass());
    public MyObjectClass MyObject { get; set; }
}

但是,当我通过ReflectionSetValueMyObject时,MyObjectClass也被初始化了。我该如何解决这个问题,或者让我知道我是否应该使用完全不同的解决方案。

简答

你可能不喜欢,但是你不能对自动实现的属性进行延迟加载 u̲n̲l̲e̲s̲s̲:

  • 你有 container/factory,它在你的 class 上创建了一个代理,并通过代理处理延迟加载。
  • 或者你有一些机制,比如使用编织工具在构建时操纵生成的 IL,并将延迟加载功能添加到你的 class。

我怀疑您是否真的需要使用自动实现的属性进行延迟加载。所以我的建议是:更改您的代码并忘记自动实现的属性,并使用自定义 getter 和 setter 轻松使用延迟加载(使用或不使用 lazy<T>.

无论如何,如果您想了解更多,请阅读详细答案。

Note: By doing some research, you may find some posts in Whosebug which are trying to address similar questions, for example: this post, or this one. I've tried to share more details/options here in this post. You may also find the linked posts useful.

详细解答 - 自动实现属性的延迟加载

您想在 class 中加入 Lazy Initialization for Auto-Implemented Property

正如简短回答中已经提到的那样,您不能对自动实现的属性进行延迟加载,除非:

  • 你有 container/factory,它在你的 class 上创建了一个代理,并通过代理处理延迟加载。
  • 或者你有一些机制,比如使用编织工具在构建时操纵生成的 IL,并将延迟加载功能添加到你的 class。

例如在Entity Framework中,您可以延迟加载虚拟自动属性。但问题是,它由 Entity Framework 处理,在这种情况下,它会为对象创建代理。

延迟加载自动实现的属性 - 选项

要实施上述解决方案之一,您可以使用以下选项:

要扩展解决方案,您可以使用以下想法生成代理并在代理中处理延迟加载:

  • 自己实现代理class:你可以创建一个代理class从你的主class派生然后覆盖你想要延迟加载的 属性 。然后你可以在你的 DI 容器中注册派生 class 并且 return 派生 class 的一个实例,只要你作为主 class 请求的实例。

  • 使用Reflection.Emit在运行时实现一个通用工厂来创建代理:你可以创建一个通用工厂方法来使用 Reflection.Emit 或其他 运行 时间程序集生成机制在 运行 时间创建代理。

  • 使用RealProxy在运行时实现一个通用工厂来创建代理您可以创建一个通用工厂并使用RealProxy 拦截属性 的getter 和setter 并在proxy 的属性 添加懒加载特性

  • 依赖依赖注入框架的拦截特性: 你可以使用你的依赖注入框架的拦截特性(如果它有这样的特性)并拦截get和set并实现延迟加载。例如,您可以在 Unity 中使用拦截器来实现该功能。

  • 依靠编织工具在构建时操作程序集:您可以依赖 AOP 框架和编织工具,如 Fody or PostSharp to manipulate the IL of your assembly and make the property lazy-loadable at compile time. For example you can find an implementation for PostSharp here

问题与get方法有关。

第一种情况你有:

public class MyClass
{
    private Lazy<MyObjectClass> lazyObjectClass = new Lazy<MyObjectClass>(() => new MyObjectClass());

    public MyObjectClass MyObject { get { return lazyObjectClass.Value;** } }
}

当你访问 MyObject 时,实际上你调用了一个从你的 lazyObjectClass 读取的 get 方法,所以 lazyObjectClass 只有在它被调用时才被评估。

在第二种情况下,你使用了一个自动属性,但真正的代码是这样的:

public class MyClass
{
    private Lazy<MyObjectClass> lazyObjectClass = new Lazy<MyObjectClass>(() => new MyObjectClass());

    private MyObjectClass <MyObject>k__BackingField;
    public MyObjectClass MyObject 
    { 
       get 
       {
          return <MyObject>k__BackingField;
       }
       set
       {
          <MyObject>k__BackingField = value;
       }
    }
}

但是有一个问题,get方法直接从隐藏的自动生成的k__BackingField中读取,必须已经被评估,没有调用lazy,你可以' t change get implementation with reflection.

没有办法按照你的想法去做,但我在这里建议这个解决方法:

public class MyObjectClass
    {

    }

  public class MyClass
    {
        public MyClass()
        {
            var field = this.GetType().GetField("lazyObjectClass", BindingFlags.NonPublic | BindingFlags.Instance);
            //Get value of field
            Lazy<MyObjectClass> lazyValue = (Lazy<MyObjectClass>)field.GetValue(this);

            //Get property MyObject
            var lazyField = this.GetType().GetField("_lazy", BindingFlags.NonPublic | BindingFlags.Instance);
            lazyField.SetValue(this, lazyValue);

        }

        private Lazy<MyObjectClass> _lazy = null;
        private MyObjectClass myValue = null;

        private Lazy<MyObjectClass> lazyObjectClass = new Lazy<MyObjectClass>(() =>
        {
            return new MyObjectClass();
        });
        public MyObjectClass MyObject
        {
            get
            {
                if (myValue == null)
                {
                    return _lazy.Value;
                }
                else
                {
                    return myValue;
                }

            }
            set
            {
                myValue = value;
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();

            var a = myClass.MyObject;
        }
    }