Dipende ... che specificamente dire che non vuoi un generico classe ... l'unica altra opzione è un metodo generico in una non generica class . L'unica volta altro è possibile ottenere il compilatore ad emettere una chiamata constrained
è se si chiama ToString()
, GetHashCode()
o Equals()
(da object
) su un struct
, dal momento che questi sono allora constrained
- se il struct
ha un override
saranno call
; se non ha un override
, sarà callvirt
. È per questo che dovresti sempre override
quelli 3 per qualsiasi struct
; p Ma sto divagando.Un semplice esempio potrebbe essere una classe di utilità con alcuni metodi statici: l'estensione sarebbe un esempio ideale, poiché si ha anche il vantaggio che il compilatore passerà automaticamente tra l'API pubblica/implicita e l'API estensione/esplicita, senza di te sempre bisogno di cambiare codice. Ad esempio, il seguente (che mostra sia un'implementazione implicita ed esplicita) non ha la boxe, con un call
e uno constrained
+ callvirt
, che sarà attuata tramite call
al JIT:
using System;
interface IFoo
{
void Bar();
}
struct ExplicitImpl : IFoo
{
void IFoo.Bar() { Console.WriteLine("ExplicitImpl"); }
}
struct ImplicitImpl : IFoo
{
public void Bar() {Console.WriteLine("ImplicitImpl");}
}
static class FooExtensions
{
public static void Bar<T>(this T foo) where T : IFoo
{
foo.Bar();
}
}
static class Program
{
static void Main()
{
var expl = new ExplicitImpl();
expl.Bar(); // via extension method
var impl = new ImplicitImpl();
impl.Bar(); // direct
}
}
Ed ecco i bit della chiave di iL:
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] valuetype ExplicitImpl expl,
[1] valuetype ImplicitImpl impl)
L_0000: ldloca.s expl
L_0002: initobj ExplicitImpl
L_0008: ldloc.0
L_0009: call void FooExtensions::Bar<valuetype ExplicitImpl>(!!0)
L_000e: ldloca.s impl
L_0010: initobj ImplicitImpl
L_0016: ldloca.s impl
L_0018: call instance void ImplicitImpl::Bar()
L_001d: ret
}
.method public hidebysig static void Bar<(IFoo) T>(!!T foo) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
.maxstack 8
L_0000: ldarga.s foo
L_0002: constrained. !!T
L_0008: callvirt instance void IFoo::Bar()
L_000d: ret
}
uno svantaggio di un metodo di estensione, però, è che si sta facendo una copia extra del struct
(vedi ldloc.0
) nello stack, che potrebbe essere un problema se è sovradimensionato, o se è un metodo di muting (che dovresti evitare comunque). In questo caso, è utile un parametro ref
, ma nota che un metodo di estensione 10 non può avere un parametro ref this
, quindi non è possibile farlo con un metodo di estensione. Ma prendere in considerazione:
Bar(ref expl);
Bar(ref impl);
con:
static void Bar<T>(ref T foo) where T : IFoo
{
foo.Bar();
}
che è:
L_001d: ldloca.s expl
L_001f: call void Program::Bar<valuetype ExplicitImpl>(!!0&)
L_0024: ldloca.s impl
L_0026: call void Program::Bar<valuetype ImplicitImpl>(!!0&)
con:
.method private hidebysig static void Bar<(IFoo) T>(!!T& foo) cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: constrained. !!T
L_0007: callvirt instance void IFoo::Bar()
L_000c: ret
}
Ancora nessuna boxe, ma ora abbiamo anche mai copiare lo struct , anche per 01 esplicitocaso.
(Credo che questo dovrebbe essere un commento :)) Vedi http://stackoverflow.com/questions/3032750/struct-interfaces-and-boxing – Matthias
grazie per la tua risposta. Ho modificato il titolo: come chiamare tranne in generico? – Vince
Per quanto mi riguarda, non ho letto il post completo, perché dal titolo mi aspettavo qualcosa di diverso :) Alla tua domanda: I * guess * no. – Matthias