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

아니 없어. 자동 구현 속성은 가장 기본적인 속성 인 getter 및 setter를 사용한 지원 필드 만 구현합니다. 이러한 유형의 사용자 지정을 지원하지 않습니다.

그러나 4.0 Lazy<T>유형을 사용하여이 패턴을 만들 수 있습니다.

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

이 코드는 표현식이 _someVariable처음 Value호출 될 때의 값을 느리게 계산합니다 . 한 번만 계산되며 향후 Value속성 사용을 위해 값을 캐시합니다.


1
실제로 Lazy가 싱글 톤 패턴을 구현하는 것처럼 보입니다. 그것은 내 목표가 아닙니다 ... 내 목표는 느리게 인스턴스화되지만 그것이 살고있는 클래스의 인스턴스와 함께 처리되는 지연로드 된 속성을 만드는 것입니다. Lazy는 그런 방식으로 수행하지 않는 것 같습니다.
ctorx

19
@ctorx Lazy는 싱글 톤 패턴과 관련이 없습니다. 그것은 당신이 원하는 것을 정확하게 수행합니다.
user247702 2013

8
참고 SomeClass.IOnlyWantToCallYouOnce귀하의 예제에서 필드 이니셜 라이저와 함께 사용하기에 고정해야합니다.
rory.ap

멋진 대답입니다. 지연 속성이 많을 것으로 예상되는 경우 사용할 수있는 Visual Studio 스 니펫에 대한 내 대답을 참조하십시오.
Zephryl

40

아마도 가장 간결한 것은 null-coalescing 연산자를 사용하는 것입니다.

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

10
IOnlyWantToCallYouOnce반환 하는 경우 null두 번 이상 호출합니다.
JaredPar

9
null-coalescing 연산자를 사용하면 위의 예가 실패합니다. 올바른 구문은 다음과 같습니다. _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- _SomeVariablenull 인 경우 설정 주위에 괄호가 추가되었습니다 .
Metro Smurf

이것이 최선의 선택입니다. 처음에는을 사용 Lazy<>했지만 우리의 목적 상 더 잘 작동했습니다. 최신 C #을 사용하면 훨씬 더 간결하게 작성할 => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());수 있습니다. 첫 번째보기에서 눈치 채지 못할 수있는 것은 연산자 가 오른쪽 피연산자를 평가하고 그 결과를 반환 한다는 입니다.
RunninglVlan

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

다른 말로하면 이것은 지연로드되지 않습니까?
Zapnologica

@Zapnologica 내 이전 답변이 약간 잘못되었지만 업데이트했습니다. SomeVariable게으른로드입니다.
Alexander Derck

이 답변은 Expression Bodied Auto-Properties에 대한 피치와 비슷합니다.
Little Endian

@AbleArcher 새로운 언어 기능을 지적하는 것이 이제 피치입니까?
Alexander Derck

5

그렇지 않으면 속성에 대한 매개 변수는 값이 일정해야하며 코드를 호출 할 수 없습니다 (정적 코드라도).

그러나 PostSharp의 Aspect로 무언가를 구현할 수 있습니다.

한번 봐봐:

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

@Riera 무슨 뜻이야? 일반 재산과 같습니다. 예 public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

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
이것은 저자의 질문에 답할 수 있지만 설명하는 단어와 문서 링크가 부족합니다. 원시 코드 조각은 주변에 일부 문구가 없으면 그다지 도움이되지 않습니다. 좋은 답변을 작성하는 방법이 매우 도움 이 될 수도 있습니다. 답변을 수정하십시오.
hellow

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.