2009-10-21 2 views
51

Ho una classe come di seguito:un costruttore come delegato - è possibile in C#?

class Foo 
{ 
    public Foo(int x) { ... } 
} 

e ho bisogno di passare a un certo metodo di un delegato come questo:

delegate Foo FooGenerator(int x); 

E 'possibile passare al costruttore direttamente come valore FooGenerator, senza dover digitare:

delegate(int x) { return new Foo(x); } 

?

MODIFICA: Per uso personale, la domanda si riferisce a .NET 2.0, ma sono ben accetti suggerimenti/risposte per 3.0+.

+0

Interessante domanda. Credo che i costruttori siano effettivamente metodi per quanto riguarda il CLR, ma non conoscerei la sintassi. – Noldorin

+3

Sono interessato: perché vorresti farlo? –

+0

Sospetto che la risposta non sia comunque. – Noldorin

risposta

27

No, il CLR non consente delegati vincolanti per ConstructorInfo.

È possibile tuttavia solo creare il proprio:

static T Make<T>(Action<T> init) where T : new() 
{ 
    var t = new T(); 
    init(t); 
    return t; 
} 

Uso

var t = Make<Foo>(x => { x.Bar = "bar"; x.Baz = 1; }); 
+0

Potresti aggiungere alcuni riferimenti (link MSDN?) Per la tua dichiarazione sull'associazione a ConstructorInfo? – akavel

+7

Un costruttore non produce un nuovo oggetto. Un costruttore funziona in combinazione con una routine di allocazione. – user7116

+0

@sixlettervariables: +1 - sembra una spiegazione ragionevole. Detto questo, mi piacerebbe comunque vedere alcuni riferimenti MSDN/C# -specification/... da qualcuno. – akavel

2

Purtroppo no, i costruttori non sono esattamente le stesse cose dei metodi e in quanto tali non è possibile creare un delegato che punti ad essi. Questa è un'idea interessante però, forse con più informazioni potremmo escogitare una sorta di soluzione che sarebbe sintatticamente simile.

+0

Potresti eventualmente approfondire l'affermazione che "i costruttori non sono del tutto (...) come metodi" nel contesto dei delegati? Mi piacerebbe particolarmente alcuni riferimenti a MSDN/C# di riferimento/altri documenti. – akavel

+0

Risposta non rispondente, l'unica informazione fornita è "No" e "I costruttori non sono esattamente le stesse cose dei metodi" (ovviamente no). Sì un'altra risposta che finge di spiegare perché qualcosa non è possibile dicendo "semplicemente non è". – jwg

5

Sembra che tu probabilmente stia usando il modello di classe factory.

Factory Method Pattern

+0

Questo è in realtà ciò che avevo usato come soluzione temporanea, ma per scrivere la domanda ho pensato che il costrutto 'delegato' fosse più facile da capire. – akavel

+0

Il modello di metodo di fabbrica è buono, ma solo se si conoscono tutti i tipi possibili in fase di compilazione. –

0

La mia ipotesi è che non è possibile in quanto si potrebbe passare un metodo di un oggetto che non è stato ancora creato.

7

penso più conciso, come si sta andando ad ottenere (senza spostare ad un modello di fabbrica) sarebbe qualcosa con metodi anonimi, in questo modo:

delegate Foo FooGenerator(int x); 

...  

void DoStuff() 
{ 
    YourDelegateConsumer(x => new Foo(x)); 
} 

Questo non sta facendo assolutamente quello che hai chiesto (dal momento che stai passando un delegato a un metodo anonimo che restituisce una nuova istanza, piuttosto che un delegato diretto al costruttore), ma non penso che ciò che chiedi sia strettamente possibile.

Questo è, naturalmente, supponendo che si sta utilizzando 3.5+

+0

+1; In realtà sto compilando per 2.0, ed è per questo che ho dovuto lavorare con "delegate", ma poiché la domanda principale riguarda qualcos'altro, il costrutto lambda dovrebbe essere sicuramente ricordato. – akavel

49

Sto assumendo che normalmente fare qualcosa di simile come parte di un'implementazione fabbrica, dove i tipi effettivi aren noto al momento della compilazione ...

In primo luogo, si noti che un approccio più semplice può essere un passo di post-creazione, quindi è possibile utilizzare i generici:

static T Create<T>({args}) where T : class, ISomeInitInterface, new() { 
    T t = new T(); 
    t.Init(args); 
    return t; 
} 

È quindi possibile utilizzare MakeGenericMethod e/o CreateDelegate.


Altrimenti; puoi farlo al volo con Expression (3.5) o DynamicMethod (2.0).

Il Expression approccio è più facile da codice:

var param = Expression.Parameter(typeof(int), "val"); 
    var ctor = typeof(Foo).GetConstructor(new[] { typeof(int) }); 
    var lambda = Expression.Lambda<Func<int, Foo>>(
     Expression.New(ctor, param), param); 
    var func = lambda.Compile(); 
    Foo foo = func(123); 
    string s = foo.ToString(); // proof 

o (utilizzando DynamicMethod):

ConstructorInfo ctor = typeof(Foo).GetConstructor(new[] { typeof(int) }); 
    DynamicMethod dm = new DynamicMethod("Create", typeof(Foo), 
      new Type[] { typeof(int) }, typeof(Foo), true); 
    ILGenerator il = dm.GetILGenerator(); 
    il.Emit(OpCodes.Ldarg_0); 
    il.Emit(OpCodes.Newobj, ctor); 
    il.Emit(OpCodes.Ret); 
    Converter<int, Foo> func = (Converter<int, Foo>) 
     dm.CreateDelegate(typeof(Converter<int, Foo>));   
    Foo foo = func(123); 
    string s = foo.ToString(); // proof 
+1

Uh oh; tecnicamente, usando la riflessione & c. è corretto (e ci ho pensato anche per un momento), ma ha gravi difetti: 1) come visto nel tuo commento, danneggia seriamente la leggibilità (e rende il codice meno conciso invece di più); 2) AFAIK, la riflessione è più lenta dei costrutti supportati dalla lingua, poiché impone un ulteriore livello di astrazione. – akavel

+5

Una volta compilato per un delegato, un approccio basato sulla riflessione non è più lento e può (occasionalmente) essere più veloce del normale codice compilato. Ovviamente lo si compila solo una volta e si memorizza nella cache il delegato. –

+3

+1. Questo (l'implementazione dell'espressione) mi è stato più utile della risposta accettata, poiché avevo bisogno del cctor, non del ctor. –

2
risposta di

Marc Gravell mi ha ispirato al seguente soluzione molto semplice:

static void Main() 
{ 
    Pet a = _MakeObject(typeof(Dog)); 
    Pet b = _MakeObject(typeof(Cat)); 
} 

private static Pet _MakeObject(Type type) 
{ 
    ConstructorInfo info = type.GetConstructor(new Type[0]); 
    return (Pet)info?.Invoke(null); 
} 

Quasi la stessa cosa se il costruttore ha param (in th è un esempio: 1 parametro di tipo int):

static void Main() 
{ 
    Pet a = _MakeObject(typeof(Dog), 5); 
    Pet b = _MakeObject(typeof(Cat), 7); 
} 

private static Pet _MakeObject(Type type, int age) 
{ 
    ConstructorInfo info = type.GetConstructor(new [] { typeof(int) }); 
    return (Pet)info?.Invoke(new object[] { age }); 
}