Proprietà asincrone nelle interfacce per soddisfare la possibilità di costose valutazioni per la prima volta: è una buona idea?

0

Prima di tutto, scusa se questo post è troppo lungo. Inizierò con ...

Versione breve:

È generalmente consigliabile o una buona idea progettare una proprietà di interfaccia come asincrona semplicemente perché non possiamo essere sicuri che ogni singola implementazione possa garantire che la sua valutazione sarà a buon mercato? Cioè, può essere una buona idea progettare un'interfaccia come questa:

interface IFoo
{
    Task<IEnumerable<Bar>> BarsAsync { get; }
}

invece di questo:

interface IFoo
{
    IEnumerable<Bar> Bars { get; }
}

Versione lunga:

Ora lasciami spiegare come sono arrivato a questa domanda di design.

Iniziamo con la versione non asincrona dell'interfaccia (mostrata sopra)

Le linee guida di progettazione della struttura di Microsoft richiedono che il valore di una proprietà è economico; ma quando programmiamo contro un'interfaccia, invece che contro un'implementazione particolare di IFoo , non possiamo essere certi che ogni implementazione di IFoo che potremmo utilizzare possa e aderirà a tale linea guida.

Ciò che ciascuna implementazione potrebbe fare è garantire "economicità ammortizzata", cioè memorizzare il valore della proprietà al suo primo calcolo, in modo che almeno gli accessi successivi siano a buon mercato. Eric Lippert scrive nel suo articolo del blog "Quando dovrei scrivere una proprietà?" :

A common pattern is for a property to be a cache of an expensive, lazily-computed value […]. Though the property is expensive on its first access, every subsequent access is cheap, so the property is cheap in an amortized sense.

Prosegue suggerendo di utilizzare Lazy<T> come dettaglio di implementazione verso tale fine, quindi diamo un'occhiata a una possibile implementazione dell'interfaccia precedente dove valutare Bars per la prima volta sarebbe costoso, ma non per le chiamate successive:

internal class ExpensiveFoo : IFoo
{
    private readonly Lazy<IEnumerable<Bar>> bars;
    public IEnumerable<Bar> Bars { get { return bars.Value; } }
    public ExpensiveFoo() { bars = new Lazy<IEnumerable<Bar>>(GetBars); }
    private IEnumerable<Bar> GetBars() { … /* some expensive computation */ }
}

Ciò che mi chiedo a questo punto è, non sarebbe meglio semplicemente perdere la possibilità di una costosa prima valutazione nell'interfaccia e cambiarla in:

interface IFoo
{
    Task<IEnumerable<Bar>> BarsAsync { get; }
}

Ciò ti consentirebbe di scrivere implementazioni sia "sempre a buon mercato" sia implementazioni "a costi ridotti" (come sopra), con la consapevolezza che il consumatore ne è consapevole.

internal class ExpensiveFoo : IFoo
{
    private Lazy<Task<IEnumerable<Bar>>> bars;
    public Task<IEnumerable<Bar>> BarsAsync { get { return bars.Value; } }
    public ExpensiveFoo() { bars = new Lazy<Task<IEnumerable<Bar>>>(GetBarsAsync); }
    private async Task<IEnumerable<Bar>> GetBarsAsync() { … };
}

internal class CheapFoo : IFoo
{
    public Task<IEnumerable<Bar>> BarsAsync { get { return Task.FromResult(bars) } }
    private IEnumerable<Bar> bars;
    public CheapFoo(IEnumerable<Bar> bars) { this.bars = bars; }
}

Indipendentemente dall'implementazione utilizzata, è possibile scrivere codice che potenzialmente non si blocca anche sul primo accesso alle proprietà:

Foo foo = …;
foreach (Bar bar in await foo.BarsAsync) …

Questa è sempre una buona idea?

    
posta stakx 23.11.2014 - 13:38
fonte

1 risposta

2

Una delle ragioni per cui le proprietà non dovrebbero mai essere "fantasiose" in termini di implementazione, in genere contengono codice che viene eseguito nel debugger, nelle finestre di controllo e in altri luoghi in cui è possibile visualizzare le variabili, ad esempio suggerimenti. I metodi vengono eseguiti solo su richiesta.

Alcuni tipi di proprietà "fantasiose":

  • Quelli che allocano memoria quando calcolano il loro valore.
  • Quelli che non tornano in tempo costante.
  • Quelli che ritornano in tempo costante, ma è un tempo "lungo" (maggiore di pochi millisecondi)
  • Coloro che hanno effetti collaterali: alcuni sostengono che questo può essere ok se l'effetto collaterale è quello di inizializzare un campo pigramente. Ma anche questo può causare un comportamento diverso quando si collega un debugger.
risposta data 27.11.2014 - 23:01
fonte