Rilevamento di "macchine di stato" numeriche

17

Ho appena letto un interessante articolo chiamato Stai diventando troppo carino con c # yield return

Mi sono chiesto quale sia il modo migliore per rilevare se un oggetto IEnumerable è una collezione enumerabile o se è una macchina a stati generata con la parola chiave yield.

Ad esempio, puoi modificare DoubleXValue (dall'articolo) a qualcosa di simile:

private void DoubleXValue(IEnumerable<Point> points)
{
    if(points is List<Point>)
      foreach (var point in points)
        point.X *= 2;
    else throw YouCantDoThatException();
}

Domanda 1) Esiste un modo migliore per farlo?

Domanda 2) È qualcosa di cui dovrei preoccuparmi quando creo un'API?

    
posta ConditionRacer 21.01.2013 - 17:21
fonte

3 risposte

22

La tua domanda, a quanto ho capito, sembra basata su una premessa errata. Fammi vedere se riesco a ricostruire il ragionamento:

  • L'articolo linkato descrive come le sequenze generate automaticamente mostrano un comportamento "pigro" e mostra come ciò possa portare a un risultato contro-intuitivo.
  • Quindi posso rilevare se una determinata istanza di IEnumerable mostra questo comportamento pigro controllando per vedere se viene generata automaticamente.
  • Come faccio?

Il problema è che la seconda premessa è falsa. Anche se si potesse rilevare se un dato oggetto IEnumerable fosse o meno il risultato di una trasformazione del blocco iteratore (e sì, ci sono modi per farlo) non sarebbe d'aiuto in quanto l'assunto è sbagliato. Illustriamo il motivo.

class M { public int P { get; set; } }
class C
{
  public static IEnumerable<M> S1()
  {
    for (int i = 0; i < 3; ++i) 
      yield return new M { P = i };
  }

  private static M[] ems = new M[] 
  { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  public static IEnumerable<M> S2()
  {
    for (int i = 0; i < 3; ++i)
      yield return ems[i];
  }

  public static IEnumerable<M> S3()
  {
    return new M[] 
    { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  }

  private class X : IEnumerable<M>
  {
    public IEnumerator<X> GetEnumerator()
    {
      return new XEnum();
    }
    // Omitted: non generic version
    private class XEnum : IEnumerator<X>
    {
      int i = 0;
      M current;
      public bool MoveNext()
      {
        current = new M() { P = i; }
        i += 1;
        return true;
      }
      public M Current { get { return current; } }
      // Omitted: other stuff.
    }
  }

  public static IEnumerable<M> S4()
  {
    return new X();
  }

  public static void Add100(IEnumerable<M> items)
  {
    foreach(M item in items) item.P += 100;
  }
}

Va bene, abbiamo quattro metodi. S1 e S2 sono sequenze generate automaticamente; S3 e S4 sono sequenze generate manualmente. Ora supponiamo di avere:

var items = C.Sn(); // S1, S2, S3, S4
S.Add100(items);
Console.WriteLine(items.First().P);

Il risultato per S1 e S4 sarà 0; ogni volta che si enumera la sequenza, si ottiene un nuovo riferimento a una M creata. Il risultato per S2 e S3 sarà 100; ogni volta che si enumera la sequenza, si ottiene lo stesso riferimento a M ottenuto l'ultima volta. Se il codice di sequenza viene generato automaticamente o meno è ortogonale alla domanda se gli oggetti enumerati abbiano o meno un'identità referenziale. Queste due proprietà - generazione automatica e identità referenziale - in realtà non hanno nulla a che fare con l'un l'altro. L'articolo che hai collegato li confonde un po '.

A meno che un fornitore di sequenze non sia documentato come offrire gli oggetti con identità referenziale , non è saggio presumere che lo faccia.

    
risposta data 21.01.2013 - 19:30
fonte
10

Penso che il concetto chiave sia che dovresti trattare qualsiasi IEnumerable<T> raccolta come immutabile : non devi modificare gli oggetti da essa, dovresti creare una nuova collezione con nuovi oggetti:

private IEnumerable<Point> DoubleXValue(IEnumerable<Point> points)
{
    foreach (var point in points)
        yield return new Point(point.X * 2, point.Y);
}

(Oppure scrivi lo stesso in modo più sintetico usando LINQ Select() .)

Se in effetti desideri modificare gli elementi nella raccolta, non utilizzare IEnumerable<T> , usa IList<T> (o possibilmente IReadOnlyList<T> se non vuoi modificare la raccolta stessa, solo gli elementi in essa contenuti, e tu sei su .Net 4.5):

private void DoubleXValue(IList<Point> points)
{
    foreach (var point in points)
        point.X *= 2;
}

In questo modo, se qualcuno tenta di utilizzare il tuo metodo con IEnumerable<T> , fallirà al momento della compilazione.

    
risposta data 21.01.2013 - 17:57
fonte
5

Personalmente ritengo che il problema evidenziato in quell'articolo derivi dall'uso eccessivo dell'interfaccia IEnumerable da parte di molti sviluppatori C # in questi giorni. Sembra che sia diventato il tipo predefinito quando richiedi una collezione di oggetti - ma ci sono molte informazioni che IEnumerable semplicemente non ti dice ... in modo cruciale: se sia stato o meno reificato *.

Se trovi bisogno di rilevare se un IEnumerable è veramente un IEnumerable o qualcos'altro, l'astrazione è trapelata e probabilmente ci si trova in una situazione in cui il tipo di parametro è un po 'troppo lento. Se hai bisogno delle informazioni extra che IEnumerable non fornisce, rendi esplicito tale requisito scegliendo una delle altre interfacce come ICollection o IList .

Modifica per i downvoters Per favore torna indietro e leggi attentamente la domanda. L'OP sta chiedendo un modo per garantire che le istanze di IEnumerable siano state materializzate prima di essere passate al suo metodo API. La mia risposta risponde a questa domanda. C'è un problema più ampio riguardo al modo corretto di usare IEnumerable , e certamente le aspettative del codice che l'OP ha incollato si basano su un fraintendimento di quel problema più ampio, ma l'OP non ha chiesto come usare correttamente IEnumerable , o come lavorare con tipi immutabili. Non è giusto che mi interroghi per non aver risposto a una domanda che non era stata posta.

* Uso il termine reify , altri uso stabilize o materialize . Non credo che una convenzione comune sia stata ancora adottata dalla comunità.

    
risposta data 21.01.2013 - 17:34
fonte

Leggi altre domande sui tag