【问题标题】:C# Lazy Loaded Automatic PropertiesC# 延迟加载的自动属性
【发布时间】:2010-10-27 19:17:10
【问题描述】:

在 C# 中,

有没有办法将自动属性变成具有指定默认值的延迟加载自动属性?

本质上,我正在尝试将这个...

private string _SomeVariable

public string SomeVariable
{
     get
     {
          if(_SomeVariable == null)
          {
             _SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
          }

          return _SomeVariable;
     }
}

变成不同的东西,我可以在其中指定默认值,然后它会自动处理其余部分...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}

【问题讨论】:

  • @Gabe:请注意,如果该类从不返回 null,则该类只会被调用一次。
  • 我发现...它似乎是使用单例模式

标签: c# automatic-properties


【解决方案1】:

不,没有。自动实现的属性仅用于实现最基本的属性:带有 getter 和 setter 的支持字段。它不支持这种类型的自定义。

但是您可以使用 4.0 Lazy<T> 类型来创建此模式

private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;

此代码将在第一次调用Value 表达式时懒惰地计算_someVariable 的值。它只会计算一次,并将缓存该值以供将来使用 Value 属性

【讨论】:

  • 实际上,在我看来,Lazy 实现了单例模式。那不是我的目标……我的目标是创建一个延迟加载的属性,该属性被延迟实例化,但与它所在的类的实例一起被处置。 Lazy 似乎没有那样做。
  • @ctorx Lazy 与单例模式无关。它完全符合您的要求。
  • 注意,您的示例中的 SomeClass.IOnlyWantToCallYouOnce 必须是静态的才能与字段初始化程序一起使用。
  • 很棒的答案。如果您希望有许多惰性属性,请参阅我对 Visual Studio sn-p 的回答。
【解决方案2】:

可能最简洁的方法是使用空合并运算符:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }

【讨论】:

  • 如果IOnlyWantToCallYouOnce返回null,它将多次调用它。
  • 当使用空合并操作符时,上面的例子会失败。正确的语法是:_SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() ); - 如果设置为空,请注意在设置 _SomeVariable 周围添加括号。
  • 这是最好的选择。首先我使用了Lazy&lt;&gt;,但为了我们的目的,这效果更好。使用最新的 C#,它还可以写得更简洁=&gt; _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); 乍一看,有些人可能不会注意到运算符计算右侧操作数并返回其结果
  • C# 8 让你做public object MyProp =&gt; _myProp ??= new object();
【解决方案3】:

C#6 中有一个名为Expression Bodied Auto-Properties 的新特性,它可以让你写得更简洁:

public class SomeClass
{ 
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable 
   {
      get { return _someVariable.Value; }
   }
}

现在可以写成:

public class SomeClass
{
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable => _someVariable.Value;
}

【讨论】:

  • 在最后一段代码中,初始化实际上并不是惰性的。每次实例化类时,都会在构造过程中调用IOnlyWantToCallYouOnce
  • 也就是说这不是懒加载?
  • @Zapnologica 我之前的回答有点错误,但我更新了它。 SomeVariable 是延迟加载的。
  • 这个答案读起来更像是表达体自动属性的宣传。
  • @AbleArcher 指出一个新的语言功能现在是一种宣传吗?
【解决方案4】:

运算符 ??= 可使用 C# 8.0 及更高版本,因此您现在可以更简洁:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();

【讨论】:

  • 这是目前 C#8 及更高版本的最佳答案。
  • 不错。请注意,它不像原始版本那样是线程安全的。如果需要,Lazy&lt;T&gt; 会提供一些开销,提供内置安全性。
【解决方案5】:

不是这样,属性的参数值必须是常量,不能调用代码(即使是静态代码)。

但是,您可以使用 PostSharp 的 Aspects 来实现一些东西。

检查一下:

PostSharp

【讨论】:

    【解决方案6】:

    这是我解决您问题的方法。基本上,这个想法是一个属性,将在第一次访问时由函数设置,后续访问将产生与第一次相同的返回值。

    public class LazyProperty<T>
    {
        bool _initialized = false;
        T _result;
    
        public T Value(Func<T> fn)
        {
            if (!_initialized)
            {
                _result = fn();
                _initialized = true;
            }
            return _result;
        }
     }
    

    然后使用:

    LazyProperty<Color> _eyeColor = new LazyProperty<Color>();
    public Color EyeColor
    { 
        get 
        {
            return _eyeColor.Value(() => SomeCPUHungryMethod());
        } 
    }
    

    传递函数指针当然会产生开销,但它为我完成了这项工作,与一遍又一遍地运行该方法相比,我没有注意到太多的开销。

    【讨论】:

    • 把函数交给构造函数不是更有意义吗?这样您就不会每次都内联创建它,并且您可以在第一次使用后将其丢弃。
    • @lund.mikkel 是的,那也可以。可能是这两种方法的用例。
    • 如果你将函数传递给构造函数,就像 .Net 的 Lazy 类一样,那么传入的函数必须是静态的,我知道这在很多情况下不适合我的设计。跨度>
    • @MikkelR.Lund 有时您不想在构造函数中执行某些代码,而只是按需执行(并以支持字段的形式缓存结果)
    【解决方案7】:

    我是这个想法的忠实拥护者,我想提供以下 C# sn-p,我称之为 proplazy.sn-p。(您可以将其导入或粘贴到标准文件夹中,您可以从代码段管理器获取)

    这是它的输出示例:

    private Lazy<int> myProperty = new Lazy<int>(()=>1);
    public int MyProperty { get { return myProperty.Value; } }
    

    sn-p文件内容如下:(另存为proplazy.sn-p)

    <?xml version="1.0" encoding="utf-8" ?>
    <CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
        <CodeSnippet Format="1.0.0">
            <Header>
                <Title>proplazy</Title>
                <Shortcut>proplazy</Shortcut>
                <Description>Code snippet for property and backing field</Description>
                <Author>Microsoft Corporation</Author>
                <SnippetTypes>
                    <SnippetType>Expansion</SnippetType>
                </SnippetTypes>
            </Header>
            <Snippet>
                <Declarations>
                    <Literal>
                        <ID>type</ID>
                        <ToolTip>Property type</ToolTip>
                        <Default>int</Default>
                    </Literal>
                    <Literal>
                        <ID>field</ID>
                        <ToolTip>The variable backing this property</ToolTip>
                        <Default>myVar</Default>
                    </Literal>
                    <Literal>
                        <ID>func</ID>
                        <ToolTip>The function providing the lazy value</ToolTip>
                    </Literal>
                    <Literal>
                        <ID>property</ID>
                        <ToolTip>Property name</ToolTip>
                        <Default>MyProperty</Default>
                    </Literal>
    
                </Declarations>
                <Code Language="csharp"><![CDATA[private Lazy<$type$> $field$ = new Lazy<$type$>($func$);
                public $type$ $property$ { get{ return $field$.Value; } }
                $end$]]>
                </Code>
            </Snippet>
        </CodeSnippet>
    </CodeSnippets>
    

    【讨论】:

    • 为了安全起见,我会将myProperty 设置为readonly
    【解决方案8】:

    我是这样做的:

    public static class LazyCachableGetter
    {
        private static ConditionalWeakTable<object, IDictionary<string, object>> Instances = new ConditionalWeakTable<object, IDictionary<string, object>>();
        public static R LazyValue<T, R>(this T obj, Func<R> factory, [CallerMemberName] string prop = "")
        {
            R result = default(R);
            if (!ReferenceEquals(obj, null))
            {
                if (!Instances.TryGetValue(obj, out var cache))
                {
                    cache = new ConcurrentDictionary<string, object>();
                    Instances.Add(obj, cache);
    
                }
    
    
                if (!cache.TryGetValue(prop, out var cached))
                {
                    cache[prop] = (result = factory());
                }
                else
                {
                    result = (R)cached;
                }
    
            }
            return result;
        }
    }
    

    以后你可以像这样使用它

           public virtual bool SomeProperty => this.LazyValue(() =>
        {
            return true; 
        });
    

    【讨论】:

    • 在这种情况下如何使用“this”?
    • @Riera 你是什么意思?就像普通财产一样。例如。 public ISet&lt;String&gt; RegularProperty {get;set} public string CalculatedProperty =&gt; this.LazyValue(() =&gt; { return string.Join(",", RegularProperty.ToArray()); });
    【解决方案9】:

    我认为这在纯 C# 中是不可能的。但是你可以使用像PostSharp 这样的IL 重写器来完成它。例如,它允许您根据属性在函数之前和之后添加处理程序。

    【讨论】:

      【解决方案10】:

      https://github.com/bcuff/AutoLazy 使用 Fody 给你这样的东西

      public class MyClass
      {
          // This would work as a method, e.g. GetSettings(), as well.
          [Lazy]
          public static Settings Settings
          {
              get
              {
                  using (var fs = File.Open("settings.xml", FileMode.Open))
                  {
                      var serializer = new XmlSerializer(typeof(Settings));
                      return (Settings)serializer.Deserialize(fs);
                  }
              }
          }
      
          [Lazy]
          public static Settings GetSettingsFile(string fileName)
          {
              using (var fs = File.Open(fileName, FileMode.Open))
              {
                  var serializer = new XmlSerializer(typeof(Settings));
                  return (Settings)serializer.Deserialize(fs);
              }
          }
      }
      

      【讨论】:

        【解决方案11】:

        如果您在延迟初始化期间使用构造函数,那么以下扩展也可能会有所帮助

        public static partial class New
        {
            public static T Lazy<T>(ref T o) where T : class, new() => o ?? (o = new T());
            public static T Lazy<T>(ref T o, params object[] args) where T : class, new() =>
                    o ?? (o = (T) Activator.CreateInstance(typeof(T), args));
        }
        

        用法

            private Dictionary<string, object> _cache;
        
            public Dictionary<string, object> Cache => New.Lazy(ref _cache);
        
                            /* _cache ?? (_cache = new Dictionary<string, object>()); */
        

        【讨论】:

        • 使用你的助手比LazyInitializer.EnsureInitialized()有优势吗?因为据我所知,除了上述功能之外,LazyInitializer 还提供错误处理和同步功能。 LazyInitializer source code.
        【解决方案12】:
        [Serializable]
        public class ReportModel
        {
            private readonly Func<ReportConfig> _getReportLayout;
            public ReportModel(Func<ReportConfig> getReportLayout)
            {
                _getReportLayout = getReportLayout;
            }
        
            private ReportConfig _getReportLayoutResult;
            public ReportConfig GetReportLayoutResult => _getReportLayoutResult ?? (_getReportLayoutResult = _getReportLayout());
        
        
            public string ReportSignatureName => GetReportLayoutResult.ReportSignatureName;
            public string ReportSignatureTitle => GetReportLayoutResult.ReportSignatureTitle;
            public byte[] ReportSignature => GetReportLayoutResult.ReportSignature;
        }
        

        【讨论】:

        • 虽然这可能会回答作者的问题,但它缺少一些解释性文字和文档链接。如果没有围绕它的一些短语,原始代码 sn-ps 并不是很有帮助。您可能还会发现how to write a good answer 非常有帮助。请编辑您的答案。
        猜你喜欢
        • 2017-12-28
        • 2013-07-03
        • 1970-01-01
        • 2014-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多