Propiedades automáticas de carga diferida de C #


100

C ª#,

¿Hay alguna manera de convertir una propiedad automática en una propiedad automática de carga diferida con un valor predeterminado especificado?

Esencialmente, estoy tratando de convertir esto ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

en algo diferente, donde puedo especificar el valor predeterminado y maneja el resto automáticamente ...

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

@Gabe: Tenga en cuenta que la clase solo se llamará una vez si nunca devuelve nulo.
RedFilter

Descubrí que ... parece que usa el patrón singleton
ctorx

Respuestas:


112

No no hay. Las propiedades implementadas automáticamente solo funcionan para implementar las propiedades más básicas: campo de respaldo con getter y setter. No admite este tipo de personalización.

Sin embargo, puede usar el Lazy<T>tipo 4.0 para crear este patrón

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

Este código calculará perezosamente el valor de _someVariablela primera vez Valueque se llame a la expresión. Solo se calculará una vez y almacenará en caché el valor para usos futuros de la Valuepropiedad.


1
En realidad, me parece que Lazy implementa el patrón singleton. Ese no es mi objetivo ... mi objetivo es crear una propiedad con carga diferida que se instancia de forma diferida pero que se elimina junto con la instancia de la clase en la que vive. Lazy no parece estar actuando de esa manera.
ctorx

19
@ctorx Lazy no tiene nada que ver con el patrón singleton. Hace exactamente lo que quieres que haga.
user247702

8
Tenga SomeClass.IOnlyWantToCallYouOnceen cuenta que en su ejemplo debe ser estático para ser utilizado con un inicializador de campo.
rory.ap

Respuesta impresionante. Vea mi respuesta para un fragmento de Visual Studio que puede usar si espera tener muchas propiedades perezosas.
Zephryl

40

Probablemente, lo más conciso que puede obtener es usar el operador de fusión nula:

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

10
En el caso de IOnlyWantToCallYouOncedevoluciones nulllo llamará más de una vez.
JaredPar

9
Cuando se utiliza el operador de fusión nula, el ejemplo anterior fallará. La sintaxis correcta es: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- observe la adición del paréntesis alrededor de la configuración _SomeVariablesi es nulo.
Metro Smurf

Esta es la mejor opcion. Primero usé Lazy<>, pero para nuestros propósitos esto funcionó mejor. Con la última versión de C #, también se puede escribir aún más conciso. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Lo que algunos quizás no noten desde el primer vistazo es que el operador evalúa el operando de la derecha y devuelve su resultado .
RunninglVlan

15

Hay una nueva característica en C # 6 llamada Expression Bodied Auto-Properties , que le permite escribirla un poco más limpia:

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

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

Ahora se puede escribir como:

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

   public string SomeVariable => _someVariable.Value;
}

En la última sección del código, la inicialización no es realmente lenta. IOnlyWantToCallYouOncese llamaría durante la construcción cada vez que se crea una instancia de la clase.
Tom Blodget

Entonces, en otras palabras, ¿esto no es una carga diferida?
Zapnologica

@Zapnologica Mi respuesta anterior era un poco incorrecta pero la actualicé. SomeVariabletiene carga perezosa.
Alexander Derck

Esta respuesta se parece más a un discurso para propiedades automáticas corporales de expresión.
Little Endian

@AbleArcher ¿Señalar una nueva característica de idioma es un lanzamiento ahora?
Alexander Derck

5

No así, los parámetros de los atributos deben tener un valor constante, no se puede llamar al código (incluso al código estático).

Sin embargo, es posible que pueda implementar algo con Aspectos de PostSharp.

Échales un vistazo:

PostSharp


5

Aquí está mi implementación de una solución a su problema. Básicamente, la idea es una propiedad que será establecida por una función en el primer acceso y los accesos posteriores producirán el mismo valor de retorno que el primero.

public class LazyProperty<T>
{
    bool _initialized = false;
    T _result;

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

Luego para usar:

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

Por supuesto, existe la sobrecarga de pasar el puntero de función, pero hace el trabajo por mí y no noto demasiada sobrecarga en comparación con ejecutar el método una y otra vez.


¿No tendría más sentido darle la función al constructor? De esta manera, no lo crearía en línea cada vez, y podría desecharlo después de usarlo por primera vez.
Mikkel R. Lund

@ lund.mikkel sí, eso también funcionaría. Pueden ser casos de uso para ambos enfoques.
deepee1

5
Si pasa la función al constructor, al igual que la clase Lazy de .Net, entonces la función pasada tendrá que ser estática, sé que esto no se ajusta a mi diseño en muchos casos.
crujiente

@ MikkelR.Lund A veces no desea ejecutar algún código en el constructor, sino solo a pedido (y almacenar en caché el resultado en forma de campo de respaldo)
mamuesstack

3

Soy un gran admirador de esta idea y me gustaría ofrecer el siguiente fragmento de C # que llamé proplazy.snippet (puede importarlo o pegarlo en la carpeta estándar que puede obtener en el Administrador de fragmentos)

Aquí hay una muestra de su salida:

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

Aquí está el contenido del archivo de fragmentos: (guardar como 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

No creo que esto sea posible con C # puro. Pero podría hacerlo usando un reescritor de IL como PostSharp . Por ejemplo, le permite agregar controladores antes y después de funciones según los atributos.


1

Lo hice así:

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

y luego puedes usarlo como

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

¿Cómo uso "esto" en este contexto?
Riera

@Riera ¿a qué te refieres? Como propiedad regular. Por ejemplo public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

1

Operator ?? = está disponible con C # 8.0 y versiones posteriores, por lo que ahora puede hacerlo aún más conciso:

private string _someVariable;

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

0

https://github.com/bcuff/AutoLazy usa Fody para darte algo como esto

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

y llamo como bramido

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

1
Si bien esto podría responder a la pregunta de los autores, carece de algunas palabras explicativas y enlaces a la documentación. Los fragmentos de código sin formato no son muy útiles sin algunas frases a su alrededor. También puede resultarle muy útil cómo escribir una buena respuesta . Edite su respuesta.
hola

0

Si usa un constructor durante la inicialización diferida, las siguientes extensiones también pueden ser útiles

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

Uso

    private Dictionary<string, object> _cache;

    public Dictionary<string, object> Cache => New.Lazy(ref _cache);

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */

¿Hay alguna ventaja en utilizar a su ayudante LazyInitializer.EnsureInitialized()? Porque por lo que puedo decir, además de la funcionalidad anterior, LazyInitializerproporciona manejo de errores y funcionalidad de sincronización. Código fuente de LazyInitializer .
semaj1919
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.