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: Зверніть увагу, що клас буде викликаний лише один раз, якщо він ніколи не поверне нуль.
RedFilter

Я виявив, що ... начебто використовується
одинарний

Відповіді:


112

Ні, немає. Автоматично реалізовані властивості функціонують лише для реалізації основних основних властивостей: поле резервного копіювання за допомогою getter та setter. Він не підтримує цей тип налаштування.

Однак ви можете використовувати Lazy<T>тип 4.0 для створення цього шаблону

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

Цей код ліниво обчислить значення _someVariableпершого разу, коли Valueвикликається вираз. Він буде обчислений лише один раз і буде кешувати значення для майбутнього використання Valueресурсу


1
Власне, мені здається, що Ледачий реалізує одинарний візерунок. Це не моя мета ... моя мета - створити властивість ліниво завантаженого, яке ліниво створено, але розміщене разом з екземпляром класу, в якому він живе. Ледачий, здається, не працює таким чином.
ctorx

19
@ctorx Lazy не має нічого спільного з одинарним малюнком. Це робить саме те, що ви хочете.
user247702

8
Зауважте, SomeClass.IOnlyWantToCallYouOnceу вашому прикладі має бути статично, щоб використовуватись із ініціалізатором поля.
rory.ap

Дивовижна відповідь. Дивіться мою відповідь на фрагмент Visual Studio, який ви можете використовувати, якщо очікуєте, що у вас є багато лінивих властивостей.
Зефріл

40

Напевно, найбільш стислим, що ви можете отримати, є використання оператора зведення нуля:

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

10
У випадку IOnlyWantToCallYouOnceповернення nullвін буде називати його не один раз.
JaredPar

9
При використанні оператора узгодження нуля, наведений вище приклад не вдасться. Правильний синтаксис: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- помітити додавання дужок навколо налаштування, _SomeVariableякщо воно є нульовим.
Метро Смурф

Це найкращий варіант. Спочатку я використовував Lazy<>, але для наших цілей це працювало краще. З останнім C # можна також написати ще більш стисло. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Що, можливо, хтось не помітить з першого погляду, це те, що оператор оцінює правий операнд і повертає його результат .
RunninglVlan

15

В C # 6 з'явилася нова функція під назвою Auto-властивості Expression Bodied , яка дозволяє написати її трохи чистіше:

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

@Zapnologica Моя попередня відповідь була трохи неправильною, але я її оновив. SomeVariableледаче завантажений.
Олександр Дерк

Ця відповідь більше нагадує крок для Auto-властивостей Expression Bodied.
Маленький Ендіан

@AbleArcher Визначення нової мовної функції - це крок зараз?
Олександр Дерк

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

Звичайно, є накладні витрати на передачу функціонального вказівника навколо, але це робить для мене роботу, і я не помічаю занадто багато накладних витрат порівняно із запуском методу знову і знову.


Чи не було б більше сенсу надавати функцію конструктору? Таким чином, ви не створювали б її вбудований вкладку кожен раз, і ви могли розпоряджатися нею, коли вперше використали її.
Міккель Р. Лунд

@ lund.mikkel так, це теж працюватиме. Можуть бути використані випадки для обох підходів.
deepee1

5
Якщо ви передаєте функцію конструктору, подібно до класу Lazy .Net, тоді передана функція повинна бути статичною, я знаю, що це не відповідає моєму дизайну у багатьох випадках.
хрусткий

@ MikkelR.Lund Іноді ви не хочете виконувати якийсь код у конструкторі, а лише на вимогу (і
кешуйте

3

Я великий прихильник цієї ідеї, і я хотів би запропонувати наступний фрагмент C #, який я назвав proplazy.snippet. (Ви можете імпортувати це або вставити його в стандартну папку, яку ви можете отримати у диспетчера Snippet)

Ось зразок її результату:

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 #. Але ви могли це зробити, використовуючи переписувач IL, як PostSharp . Наприклад, це дозволяє додавати обробники до і після функцій залежно від атрибутів.


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

Як я можу використовувати "це" в цьому контексті?
Рієра

@ Riera, що ти маєш на увазі? Так само, як звичайне майно. Напр. public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Олександр

1

Оператор ?? = доступний за допомогою C # 8.0 та новіших версій, тому тепер ви можете зробити це ще більш стисло:

private string _someVariable;

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

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
Хоча це може відповісти на запитання авторів, у ньому відсутні деякі пояснювальні слова та посилання на документацію. Фрагменти сирого коду не дуже корисні без певних фраз. Ви також можете знайти, як написати гарну відповідь дуже корисно. Відредагуйте свою відповідь.
привіт

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.