13 messaggi dal 01 agosto 2006
Buongiorno, ho creato una piccola classe a scopo didattico per contare il numero di elementi inseriti tra 2 DataTable utilizzando il metodo Except. La classe astratta prevede 2 tipi generici per poterla utilizzare con i dataset tipizzati (si ancora quelli...) ed eredita l'interfaccia IEqualityComparer per avere la possibilita' di indicare direttamente nella classe derivata la logica di comparazione. Questo e' codice:
        public abstract class Class1<TRow, TTable> : IEqualityComparer<TRow>
            where TTable : DataTable
            where TRow : DataRow
        {
            protected abstract bool GetComparerEquals(TRow x, TRow y);

            protected abstract int GetComparerHashCode(TRow obj);

            public bool Equals(TRow x, TRow y)
            {
                return GetComparerEquals(x, y);
            }

            public int GetHashCode(TRow obj)
            {
                return GetComparerHashCode(obj);
            }

            public int RowInserted(TTable newData, TTable oldData)
            {
                // nessun errore 
                int r1 = newData.AsEnumerable().Except(oldData.AsEnumerable()).Count();

                // errore se viene indicato comparer
                int r2 = newData.AsEnumerable().Except(oldData.AsEnumerable(), this).Count();

                return r1;
            }
        }

Il problema e' che se indico con this l'argomento comparer del metodo Except ottengo un errore di compilazione che non ho se tale argomento viene omesso.
Qualcuno mi sa gentilmente indicare dove sbaglio?
Grazie.
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao Stefano,
quando invochi il metodo AsEnumerable() sull'oggetto di tipo TTable, ti restituisce sempre e comunque un IEnumerable<DataRow>, e non un IEnumerable<TRow>.

Di conseguenza, il metodo Except si trova a lavorare con degli IEnumerable<DataRow> e si aspetta di ricevere come parametro un comparer che sia anch'esso di tipo IEqualityComparer<DataRow>.

Tu invece gli stai passando un IEqualityComparer<TRow> e questo è un problema.
Dalla documentazione su MSDN:
https://msdn.microsoft.com/en-us/library/ms132151%28v=vs.110%29.aspx
Il parametro T è controvariante (lo capisci anche dalla parola chiave in che si trova di fianco al parametro). Vuol dire che al posto di T puoi usare un tipo base (es. object è un tipo base di DataRow) ma non un tipo derivato (ed ecco perché non va bene passare TRow che è un tipo derivato da DataRow).

Sembra un po' controintuitivo, vero? L'esperienza ci dice che ovunque ci sia un parametro di tipo base (es. DataRow) sia consentito passare un oggetto di tipo derivato (es. TRow). Qui invece sembra vero il contrario.

In realtà prende subito senso se vai a guardare la tua implementazione di Equals, uno dei metodi richiesti dall'interfaccia IEqualitiComparer<T>.
Poniamo per ipotesi che tu abbia creato un'istanza di Class1 indicando il tipo derivato "MioDataRow" per il parametro TRow. L'Equals dunque si aspetta di ricevere come parametri due oggetti di tipo "MioDataRow". Proviamo a visualizzarlo:
public bool Equals(MioDataRow x, MioDataRow y) {
                return GetComparerEquals(x, y);
}

Ora, ricorda che l'Except sta lavorando con collezioni di DataRow. Come potrebbe l'Except passare un oggetto di tipo DataRow come argomento al metodo Equals che invece accetta dei MioDataRow, che sono dei tipi più specializzati?
Ovviamente non può, perché non è type-safe a compile time, ed ecco perché hai l'errore.

La soluzione consiste nel far implementare alla tua classe l'interfaccia IEqualityComparer<DataRow> (quindi senza usare il parametro TRow) oppure, se sei sicuro che TTable restituisce delle TRow, scrivi:
 int r2 = newData.AsEnumerable().Cast<TRow>().Except(oldData.AsEnumerable().Cast<TRow>(), this).Count();

Qui ho usato il metodo Cast<TRow> per convertire quelle che originariamente erano delle DataRow nel tuo tipo specializzato TRow. Ovviamente se la conversione fallisce tu avrai un errore a runtime.

Come ultima considerazione: penso che non ci sia una reale utilità pratica nel creare Class1 come una classe generica. Come hai visto, l'AsEnumerable ti restituisce comunque dei comuni DataRow. A questo punto puoi semplicemente far lavorare Class1 con i tipi base DataTable e DataRow.

Per approfondire, leggi qui sulla covarianza/controvarianza che sono concetti supportati a partire dal .NET Framework 4.
https://msdn.microsoft.com/en-us/library/dd799517%28v=vs.110%29.aspx

ciao,
Moreno
Modificato da BrightSoul il 03 febbraio 2015 23.09 -

Enjoy learning and just keep making
13 messaggi dal 01 agosto 2006
Sei stato molto gentile e chiarissimo fin dalle prime 2 righe di risposta.

Ti basta un grazie Moreno?

stefano
11.886 messaggi dal 09 febbraio 2002
Contributi
Ti basta un grazie Moreno?

Certo, è una moneta molto gradita qui nel forum :)
Se ti va, prova a rispondere anche tu a qualche quesito irrisolto.

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.