C#遅延読み込み自動プロパティ


100

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を返さない場合にのみ、クラスが呼び出されることに注意してください。
RedFilter

私はそれを発見しました...それはシングルトンパターンを使用しているようです
ctorx

回答:


112

いいえ、ありません。自動実装プロパティは、最も基本的なプロパティを実装するためにのみ機能します。ゲッターとセッターを持つバッキングフィールドです。このタイプのカスタマイズはサポートされていません。

ただし、4.0 Lazy<T>タイプを使用してこのパターンを作成できます

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

このコードは_someVariableValue式が最初に呼び出されたときに遅延の値を計算します。一度だけ計算され、Valueプロパティの将来の使用のために値をキャッシュします


1
実際、Lazyがシングルトンパターンを実装しているように見えます。それは私の目標ではありません...私の目標は、遅延してインスタンス化された遅延ロードされたプロパティを作成することですが、それが存在するクラスのインスタンスと共に破棄されます。レイジーはそのように実行していないようです。
ctorx 2010年

19
@ctorx Lazyは、シングルトンパターンとは何の関係もありません。それはあなたがしたいことを正確に行います。
user247702

8
注:このSomeClass.IOnlyWantToCallYouOnce例では、フィールド初期化子で使用するには静的でなければなりません。
rory.ap 16

素晴らしい答え。多くの遅延プロパティがあることが予想される場合に使用できるVisual Studioスニペットについては、私の回答を参照してください。
Zephryl

40

おそらくあなたが得ることができる最も簡潔なものは、null融合演算子を使用することです:

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

10
IOnlyWantToCallYouOnceリターンの場合は、nullそれを複数回呼び出します。
JaredPar

9
null結合演算子を使用すると、上記の例は失敗します。正しい構文は次のとおりです。_SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- _SomeVariablenullの場合、設定の前後に括弧が追加されていることに注意してください。
Metro Smurf、

これが最良の選択肢です。最初に私はを使用Lazy<>しましたが、私たちの目的にはこれがよりうまくいきました。最新のC#を使用すると、さらに簡潔に書くこともでき=> _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());ます。一見すると気付かないかもしれないのは、演算子が右側のオペランドを評価し、その結果を返すことです。
lVlan

15

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クラスがインスタンス化されるたびに、構築中に呼び出されます。
Tom Blodget 16

つまり、これは遅延読み込みされていませんか?
Zapnologica 2017年

@Zapnologica以前の答えは少し間違っていましたが、更新しました。SomeVariable遅延ロードされます。
Alexander Derck 2017年

この回答は、Expression Bodied Auto-Propertiesの売り込みによく似ています。
リトルエンディアン

@AbleArcher新しい言語機能を指摘することは今のピッチです?
Alexander Derck

5

それとは異なり、属性のパラメーターは値が一定でなければならず、コードを呼び出すことはできません(静的コードでも)。

ただし、PostSharpのアスペクトを使用して何かを実装できる場合があります。

それらをチェックしてください:

PostSharp


5

これがあなたの問題に対する私の解決策の実装です。基本的には、最初のアクセス時に関数によって設定されるプロパティであり、その後のアクセスでは最初と同じ戻り値が返されます。

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());
    } 
}

もちろん、関数ポインタを渡すオーバーヘッドがありますが、私にとってはそれで十分であり、メソッドを何度も実行する場合と比べて、オーバーヘッドが多すぎることに気づきません。


コンストラクターに関数を渡すほうが理にかなっていますか?このようにすると、毎回インラインで作成するのではなく、初めて使用した後に処分できます。
Mikkel R. Lund

@ lund.mikkelええ、それもうまくいくでしょう。両方のアプローチのユースケースになる場合があります。
deepee1 2014年

5
.NetのLazyクラスのように、コンストラクターに関数を渡す場合、渡される関数は静的である必要があります。多くの場合、これは設計に適合しないことがわかっています。
カリカリした

@ MikkelR.Lund場合によっては、コンストラクターで一部のコードを実行するのではなく、オンデマンドでのみ実行したい(そして、結果をバッキングフィールドの形式でキャッシュする)
mamuesstack

3

私はこのアイデアの大ファンで、proplazy.snippetを呼び出した次のC#スニペットを提供したいと思います(これをインポートするか、スニペットマネージャーから取得できる標準フォルダーに貼り付けることができます)。

その出力のサンプルを次に示します。

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

これがスニペットファイルの内容です:(proplazy.snippetとして保存)

<?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>

2

これは純粋なC#では不可能だと思います。しかし、PostSharpのようなIL リライターを使用してそれを行うことができます。たとえば、属性に応じて関数の前後にハンドラーを追加できます。


1

私はそれをこのようにしました:

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 2018

@Rieraどういう意味?通常のプロパティと同じように。例えば public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
アレクサンダーZuban

0

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);
        }
    }
}

0
[Serializable]
public class RaporImza
{
    private readonly Func<ReportConfig> _getReportLayout;
    public RaporImza(Func<ReportConfig> getReportLayout)
    {
        _getReportLayout = getReportLayout;
    }

    private ReportConfig _getReportLayoutResult;
    public ReportConfig GetReportLayoutResult => _getReportLayoutResult ?? (_getReportLayoutResult = _getReportLayout());

    public string ImzaAtanKisiAdi => GetReportLayoutResult.ReportSignatureName;

    public string ImzaAtanKisiUnvani => GetReportLayoutResult.ReportSignatureTitle;
    public byte[] Imza => GetReportLayoutResult.ReportSignature;
}

そして、私は怒鳴るように電話します

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));

1
これは著者の質問に答えるかもしれませんが、説明の言葉やドキュメントへのリンクが欠けています。生のコードスニペットは、周りにいくつかのフレーズがないとあまり役に立ちません。また、良い答えの書き方も非常に役立ちます。回答を編集してください。
2018

0

遅延初期化中にコンストラクターを使用する場合、以下の拡張機能も役立つ場合があります

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ソースコード
semaj1919

0

演算子?? =はC#8.0以降を使用して利用できるため、より簡潔に行うことができます。

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.