2015-09-22 25 views
15

Sto scrivendo una classe che rappresenta un LED. Fondamentalmente 3 uint valori per R, G e B in un campo da 0 a 255.Perché il tipo non può essere dedotto per questo metodo Clamp generico?

Sono nuovo di C# e iniziato con uint , che è più grande di 8 bit che voglio. Prima di scrivere il mio metodo Clamp ho cercato uno online e ho trovato this great looking answer suggerendo un metodo di estensione. Il problema è che non è stato possibile inferire il tipo a uint. Perchè è questo? Questo codice ha scritto uint su di esso. Devo dare esplicitamente il tipo per farlo funzionare.

class Led 
{ 
    private uint _r = 0, _g = 0, _b = 0; 

    public uint R 
    { 
     get 
     { 
      return _r; 
     } 
     set 
     { 
      _r = value.Clamp(0, 255); // nope 

      _r = value.Clamp<uint>(0, 255); // works 
     } 
    } 
} 

// https://stackoverflow.com/a/2683487 
static class Clamp 
{ 
    public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T> 
    { 
     if (val.CompareTo(min) < 0) return min; 
     else if (val.CompareTo(max) > 0) return max; 
     else return val; 
    } 
} 

un errore, utilizzando byte è la strada da percorrere, naturalmente. Ma sono ancora interessato alla risposta alla domanda.

+0

Come nota, proverei a limitare un po 'di più questo generico. Il modo in cui è scritto ogni tipo che implementa 'IComparable ' otterrà la funzione 'Clamp', che potrebbe non essere semanticamente corretta. Questa è una di quelle situazioni in cui sfortunatamente non possiamo limitare 'T' a tipi specifici, come' dove T: isa (byte, sbyte, int, uint) 'ecc. –

+1

@RonBeyer Secondo Eric Lippert, l'IComparable L'interfaccia deve fornire un ordine totale. Detto questo, perché "Clamp" non è semanticamente corretto, anche su, diciamo, archi? – Rawling

+0

@Rawling Ad esempio, 'stringa' implementa' IComparable ', che per me non sarebbe semanticamente corretto per un' Clamp' (char forse, ma non stringa). –

risposta

22

È perché si sta utilizzando 0 e 255, che sono int valori, non uint quelli. I numeri interi nudi in C# vengono sempre considerati come valori int (se rientrano nell'intervallo int).

Si sta inviando Clamp con il modulo uint.Clamp(int, int) => uint. Questo viene convertito dal compilatore a Clamp(unit, int, int) => uint. Il compilatore però si aspetta in effetti Clamp(T, T, T) => T, quindi segnala un errore poiché la combinazione dei tipi uint e int impedisce la risoluzione del tipo di elemento T.

Modificare la riga:

_r = value.Clamp(0, 255); 

a:

_r = value.Clamp(0U, 255U); 

e il codice verrà compilato. Il suffisso U dice al compilatore che il numero è un valore uint.

+1

Tuttavia questo non spiega perché non usa 'int' automaticamente, quando è soddisfatto di' uint'. Questo non verrà compilato: '_r = value.Clamp ((int) -1, (int) -22);' ma perché no? Il cast 'int' dice al compilatore che il numero è un' int', quindi perché non lo compila? –

+2

@MatthewWatson - perché 'value' è' uint'? – Corak

+0

@MatthewWatson perché T e il tipo restituito devono essere uguali. Poiché 'r' è' uint' e i due parametri sono 'int', non può dedurre quello che vuoi. –

16

Si sta chiamando Clamp<T>(T, T, T) con argomenti uint, int, int (come 0 e 255 sono int letterali).

Poiché non v'è implicit conversion da un tipo all'altro, il compilatore non può lavorare fuori se fare Tint o uint.

+0

Si dice che non c'è alcuna conversione implicita in entrambe le direzioni, ma in questo caso gli ultimi due argomenti, '0' e' 255', sono costanti in tempo di compilazione di tipo 'int', e quindi esiste un'espressione [implicita __constant__ conversione] (https://msdn.microsoft.com/da-dk/library/aa691286.aspx) _from_ 'int' _to_' uint'. Se così non fosse, come funzionerebbe 'value.Clamp (0, 255)? Non appena si dà esplicitamente il 'T' del metodo generico, viene applicata la conversione implicita di espressione costante. Ma vedi la nuova risposta di Lippert per i dettagli. –

4

Quando un intero letterale non ha suffisso, il suo tipo è il primo di questi tipi in cui è possibile rappresentare il suo valore: int, uint, long, ulong. Stai usando 0 e 255, che vanno bene in int in modo che uno è scelto.

si può dire al compilatore di utilizzare uint semplicemente suffisso letterale

_r = value.Clamp(0U, 255U); 

Maggiori informazioni si possono trovare da documentation.

20

Le altre risposte sono corrette ma c'è un punto sottile qui che ho pensato dovrebbe essere chiamato in modo specifico.

Normalmente in C#, il tipo di un intero letterale è int, ma può essere convertito implicitamente in qualsiasi tipo numerico in cui la costante è nell'intervallo. Pertanto, anche senon è implicitamente convertibile in uint, l'assegnazione myuint = 123; è legale perché lo standard int si adatta.

Da questo fatto è facile cadere nella convinzione errata che i valori letterali int possano essere utilizzati ovunque sia previsto un valore uint, ma hai scoperto perché questa convinzione è falsa.

L'algoritmo di inferenza del tipo è simile a questo. (Questa è una enorme semplificazione naturalmente; lambda rendono notevolmente più complessa.)

  • Calcolare i tipi degli argomenti
  • analizzare le relazioni tra argomenti e corrispondenti parametri formali
  • Da questa analisi, tipo dedurre limiti su parametri di tipo generico
  • Controllare i limiti per entrambi la completezza - ogni parametro di tipo generico deve avere limiti di limite e coerenza non devono essere contraddittori. Se l'inferenza è incompleta o incoerente, il metodo è inapplicabile.
  • Se i tipi dedotti violano i loro vincoli, il metodo è inapplicabile.
  • In caso contrario, il metodo con i tipi dedotti viene aggiunto all'insieme di metodi utilizzati per la risoluzione di sovraccarico.

La risoluzione di sovraccarico procede quindi a confrontare i metodi del candidato insieme l'uno contro l'altro per trovare il migliore.

(Si noti che il tipo di ritorno è, naturalmente, in nessun luogo considerato; C# verifica se il tipo di ritorno può essere assegnato a qualsiasi cosa viene assegnato a dopo risoluzione di sovraccarico ha scelto il metodo, non durante la risoluzione di sovraccarico.)

Nel tuo caso l'inferenza del tipo non riesce nel passaggio "verifica che ci sia un insieme coerente di limiti". T è limitato a entrambi int e uint. Questa è una contraddizione, quindi il metodo non viene nemmeno aggiunto al set di metodi da considerare per la risoluzione del sovraccarico. Il fatto che gli argomenti siano convertibili in uint non viene mai considerato; il motore di inferenza del tipo funziona esclusivamente sui tipi.

L'algoritmo di inferenza del tipo non "arretra" in alcun modo nel proprio scenario; non dice "OK, non posso inferire un tipo consistente per T, ma forse uno dei singoli tipi funziona. Che cosa succede se ho provato entrambi i limiti int e uint? Possiamo vedere se uno di loro in realtà produce un metodo che funziona ". (Lo fa lo stesso quando sono coinvolti lambda, il che può indurlo a provare arbitrariamente molte combinazioni possibili di tipi in alcuni scenari.) Se l'algoritmo di inferenza funzionasse in questo modo otterresti il ​​risultato che desideri, ma non.

In sostanza la filosofia è che l'algoritmo di inferenza non sta cercando di trovare un modo per far funzionare il programma, ma piuttosto di trovare una catena di ragionamento sui tipi che deriva una conclusione logica unico da informazioni provenienti da gli argomenti. C# cerca di fare ciò che l'utente intende fare, ma cerca anche di evitare di indovinare; in questo caso invece di indovinare potenzialmente, è necessario essere chiari sui tipi che si intende dedurre.