2013-02-07 8 views
8

Ho funzioni pubbliche in questo modo:compilatore non chiama appropriata sovraccarico generico quando viene passato con il tipo di valore

public static T Get<T>(this Mango m, T defaultValue = default(T)) where T : class 
{ 
    //do something; return something; 
} 

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct 
{ 
    //do something; return something; 
} 

Fondamentalmente voglio gestire individualmente i tipi di riferimento e tipi nullable. Compila; fino a quando non chiedo i tipi di valore. Per i tipi di riferimento che compila.

mango.Get<string>(); // compiles.. 
mango.Get(""); // compiles.. 

mango.Get<int>(); // The type 'int' must be a reference type in order to use it as 
        // parameter 'T' in the generic type or method Get<T>(Mango, T) 
//also   // The call is ambiguous between the following methods or properties: 
        // Get<int>(Mango, int) and Get<int>(Mango, int?) 

Quale vera ambiguità è qui? Quando T è int, non si può chiamare correttamente il sovraccarico della struct? anche:

mango.Get<int>(0); // The type 'int' must be a reference type in order to use it as 
        // parameter 'T' in the generic type or method Get<T>(Mango, T) 

Perché il compilatore rilevare solo il sovraccarico di tipo di riferimento? Ho provato avente due overload distinte:

public static T Get<T>(this Mango m) where T : class 
{ 
    return default(T); 
} 

public static T? Get<T>(this Mango m) where T : struct 
{ 
    return default(T); 
} 

public static T Get<T>(this Mango m, T def) where T : class 
{ 
    return default(T); 
} 

public static T? Get<T>(this Mango m, T? def) where T : struct 
{ 
    return default(T); 
} 

Il problema persisteva. Ovviamente, i primi due metodi non vengono compilati qui poiché l'overloading non funziona semplicemente sulla base di vincoli.

ho provato rimuovendo la class sovraccarico vincolata e mantenendo solo il struct costretto uno, in questo modo:

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct 
{ 
    //do something; return something; 
} 

mango.Get<int>(); // voila compiles! 
mango.Get<int>(0); // no problem at all.. 
// but now I can't have mango.Get<string>() for instance :(

Perchè sono solo a sinistra con la ridenominazione le due funzioni? Mi sembra appropriato avere un nome unificato in modo che il chiamante non debba preoccuparsi dei dettagli dell'implementazione, ma chiama semplicemente Get per qualsiasi tipo.

Aggiornamento: La soluzione di Marc non funziona se devo evitare il parametro opzionale.

mango.Get<int>(); // still wouldnt work!! 

Ma c'è di più magico :(:(

public static bool IsIt<T>(this T? obj) where T : struct 
{ 
    return who knows; 
} 

public static bool IsIt<T>(this T obj) where T : class 
{ 
    return perhaps; 
} 

Con tutti i mezzi mi aspetto lo stesso bug del compilatore (secondo me) a darmi fastidio. Ma no funziona questa volta .

Guid? g = null; 
g.IsIt(); //just fine, and calls the struct constrained overload 
"abcd".IsIt(); //just fine, and calls the class constrained overload 

Quindi, se la risoluzione di sovraccarico viene prima di controllo dei vincoli come dice Marc, non dovrei ottenere lo stesso errore anche questa volta? Ma no. Perché è così?? Che diavolo sta succedendo? : x

+1

A metà del post, stavo per suggerire di provare solo la versione della struct, ma l'hai già fatto e lo compila. _Seems_ per me questo è un bug reale e dovrebbe essere segnalato. –

+1

@JohnWillemse: Nessun bug, vedere la risposta di Marcs. –

+0

@DanielHilgarth non può essere un bug, ma sicuramente se fosse supportato questa potrebbe essere stata una caratteristica utile! – nawfal

risposta

5

il controllo dei vincoli viene eseguito dopo la risoluzione di sovraccarico, IIRC; la risoluzione di sovraccarico sembra preferire la prima versione. È possibile forzarlo ad utilizzare l'altra, però:

mango.Get<int>((int?)0); 

o anche:

mango.Get((int?)0); 

Personalmente, mi piacerebbe probabilmente solo cambiare il nome per evitare l'ambiguità.

+0

tornerà da te dopo alcuni test. – nawfal

+0

@MarcGravell Ad ogni modo, forse la cosa è ... perché è necessario un sovraccarico per il non null? Un non-null può essere implicitamente castato a nullable. O magari usando parametri opzionali invece di sovraccaricare? Se il nullable int non è nullo, fai qualcosa, se no, fai altre cose e usa il non nullable int. –

+1

È interessante il motivo per cui preferisce il primo sovraccarico su 'mango.Get (0)', comunque. Ora rendi i metodi non generici e l'argomento obbligatorio, quindi scrivi questi overload: 'static void M (int? I) {}' e 'static void M (object o) {}', quindi la chiamata 'M (0) 'potrebbe boxare il' int' e chiamare il sovraccarico 'object' o racchiudere' int' in un tipo nullable e chiamare quel sovraccarico. Ma dal momento che ogni 'Nullable ' è un 'Object', e il contrario non è vero, il sovraccarico _nullable_ è più" specializzato "e deve essere preferito. Quindi 'M (0)' passa a 'M ((int?) 0)', non 'M ((oggetto) 0)'. Che dire di 'mango.Get (0)'? –

0

Lasciami provare a rispondere a me stesso.

Come dice Marc, controllo vincolo viene fatto dopo la risoluzione di sovraccarico, e tra

public static T Get<T>(this Mango m, T defaultValue = default(T)) where T : class 
{ 
    //do something; return something; 
} 

e

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct 
{ 
    //do something; return something; 
} 

risoluzione di sovraccarico preferisce la versione class. Ma questo è solo quando al compilatore viene data una scelta tra due sovraccarichi simili (senza il parametro opzionale, nel qual caso entrambi i sovraccarichi diventano uguali, trascurando il vincolo). Ora, quando viene applicato il vincolo, la chiamata non riesce per Get<int> poiché int non è class.

Le cose cambiano un po 'quando viene fornito il parametro predefinito. Se chiamo

mango.Get(0); 

Il compilatore è abbastanza in grado di chiamare il sovraccarico di destra, ma che ora accetta sovraccarico int o T where T: struct? Non ce n'è uno. Nell'esempio indicato, il secondo parametro dovrebbe essere T? e non T. Il compilatore non risolve automaticamente l'overloading applicando tutti i cast disponibili per ogni tipo di argomento. Non è un bug, ma questa è una caratteristica in meno, questo è tutto. Se faccio questo:

int? i = 0; 
mango.Get(i); 

funziona, viene chiamato il sovraccarico corretto. Questo è ciò che sta accadendo anche nel secondo esempio. Funziona perché sto fornendo il parametro giusto.

quando chiamo:

Guid? g = null; 
g.IsIt(); 

obj è noto per essere g e quindi T uguale Guid. Ma se chiamo

Guid g = Guid.NewGuid(); 
g.IsIt(); 

Questo non funziona, dal momento che è ora gGuid e non Guid? e compilatore pretende molto fare il casting automaticamente, invece si deve dire al compilatore in modo esplicito.

Io sto bene con il fatto che il compilatore non esegue il casting automaticamente poiché quello sarà troppo per calcolare per ogni tipo possibile, ma quello che sembra essere una mancanza in C# è il fatto che il controllo dei vincoli non è coinvolto nella risoluzione di sovraccarico. Ciò vale anche se fornisco il tipo mango.Get<int>() o mango.Get<int>(0) la risoluzione di sovraccarico non preferirà la versione struct e utilizzare default(int?) per l'argomento defaultValue. Sembra strano per me.

1

È interessante notare che il compilatore verificherà i vincoli specificati all'interno dei tipi generici utilizzati all'interno di una firma del metodo, ma non per i vincoli all'interno della firma stessa.

Pertanto, se un metodo accettato due parametri, uno di tipo T where T : struct oltre ad Nullable<T>[], il compilatore non considerare il metodo per qualsiasi T che non era uno struct.specificato struct vincolo del metodo per T non è considerato nella valutazione sovraccarichi, ma il fatto che vincola Nullable<T>T a struct, è.

Ho davvero trovato la totale incapacità di considerare i vincoli nella valutazione del sovraccarico bizzarro, dato che si poteva specificare un valore nullo predefinito per il parametro Nullable<T>[] e fare finta che il parametro non esistesse. I compilatori di vb.net e i compilatori C# sembrano differire, tuttavia, quando si tratta di ciò che considerano ambiguo e ciò che accettano.

+0

'il compilatore controllerà i vincoli specificati all'interno dei tipi generici che sono utilizzati all'interno di una firma del metodo, ma non per i vincoli all'interno della firma stessa. Era molto confuso, ma' Il vincolo di struct specificato per T non è considerato nella valutazione degli overload, ma il fatto che Nullable limiti T a struct, è. aiutato :) Vb inferisce correttamente il sovraccarico nel mio caso? – nawfal

+0

@nawfal: non lo fa, ma l'aggiunta di un parametro fittizio di un tipo che include un parametro di tipo generico di classe vincolata constained (nello stesso modo in cui '' Nullable include un parametro di tipo generico struct con limiti) può aiutare; vb.net percepisce ancora l'ambiguità nel caso di due firme identiche ad eccezione di un parametro dummy default-null, ma se non si ha bisogno del parametro di tipo generico all'interno della funzione, si potrebbe essere in grado di farlo prendere un parametro di scrivi 'Object'. – supercat

Problemi correlati