Questo codice genera un'eccezione
System.InvalidOperationException: il tipo di entità 'Lista <..>' non è stato trovato. Assicurarsi che il tipo di entità sia stato aggiunto al modello.
private static void Update<T>(DbContext context, ICollection<T> existing, ICollection<T> updated) // where T: class
{
context.RemoveRange(existing);
updated.ToList().ForEach(existing.Add);
}
Tuttavia, se si aggiunge il vincolo di tipo in where T: class
non viene generata alcuna eccezione. Perchè è questo? Avevo l'impressione che i vincoli di tipo C # non influissero sul comportamento del tempo di esecuzione come questo. Entrambe le versioni si compongono bene.
Non è il comportamento di runtime, ma la risoluzione del sovraccarico del metodo di compilazione e la covarianza qui:
context.RemoveRange(existing);
RemoveRange
metodo RemoveRange
ha due overload:
RemoveRange(IEnumerable<object> entities)
e
RemoveRange(params object[] entities)
Il vincolo di classe consente al compilatore C # di selezionare il sovraccarico con IEnumerable<object>
- poiché ICollection<T>
è IEnumerable<T>
e IEnumerable<T>
per il tipo di riferimento T
è covariant, quindi IEnumerable<object>
.
Senza il vincolo di classe, le uniche opzioni disponibili sono il metodo con argomento params object[]
. E qui viene uno degli inconvenienti / effetti collaterali / trappole del costrutto params object[]
- ogni singolo argomento arg
con un tipo diverso da object[]
viene trattato come object
e passato implicitamente come new object[] { arg }
.
Quindi, nel primo caso la chiamata effettiva è
context.RemoveRange((IEnumerable<object>)existing);
mentre nel caso successivo lo è
context.RemoveRange(new object[] { existing });
In altre parole, la lista viene passata come oggetto, che porta all'eccezione di runtime in questione.
Lo stesso vale per tutti gli altri Range
metodi della DbContext
di classe - AddRange
, UpdateRange
e AttachRange
.