2009-03-18 16 views
14

Indipendentemente dal fatto che questa sia o meno una buona idea, è possibile implementare un'interfaccia in cui la funzione di esecuzione è a conoscenza del tipo dell'oggetto chiamante?Determinazione del tipo di oggetto chiamante in C#

class A 
{ 
    private C; 

    public int doC(int input) 
    { 
     return C.DoSomething(input); 
    } 
} 

class B 
{ 
    private C; 

    public int doC(int input) 
    { 
     return C.DoSomething(input); 
    } 
} 

class C 
{ 
    public int DoSomething(int input) 
    { 
     if(GetType(CallingObject) == A) 
     { 
     return input + 1; 
     } 
     else if(GetType(CallingObject) == B) 
     { 
     return input + 2; 
     } 
     else 
     { 
     return input + 3; 
     } 
    } 
} 

Mi sembra come questa è una pratica di codifica cattiva (perché i parametri non cambiano, ma l'uscita sarebbe) ma a parte questo è possibile?

Sono in una situazione in cui vorrei che alcuni tipi specifici potessero chiamare una determinata funzione, ma non posso escludere l'accesso alla funzione. ho pensato di avere un parametro "tipo"

DoSomething(int input, Type callingobject) 

Ma non c'è alcuna garanzia che l'oggetto chiamante userebbe GetType (questo), al contrario di GetType (B) per lo spoofing di un B indipendentemente dal loro tipo.

Questo sarebbe semplice (relativamente semplice) come esaminare il callstack?


EDIT

si prega di votare fino risposta di JaredPar (se si vuole), così come John Feminella di. Non sono riuscito a contrassegnare entrambe le risposte come accettate e ho accettato la risposta di John Feminella perché è specificamente ciò che ho chiesto, a differenza della risposta di Jared che ha sollevato una soluzione che non avevo precedentemente preso in considerazione.

+0

Potrebbe dare un po 'più di fondo? Perché ora non vedo perché non si implementi solo metodi separati in C. Qualcosa come C.DoSometingA e C.DoSomethingB. O ancora meglio: ma il comportamento nelle classi A & B. –

+1

La versione breve è che avrei finito per avere 30 o 40 funzioni identiche che erano semplicemente fuori da una o due linee. – DevinB

+0

@devinb: vedere la modifica che ho apportato al mio post qui sotto. Puoi prendere in considerazione l'idea di aprire una domanda diversa. –

risposta

17

In primo luogo, sì, è una pessima idea farlo e rompe tutti i tipi di solidi principi di progettazione. Dovresti sicuramente considerare un approccio alternativo se è aperto, come semplicemente usando il polimorfismo - questo sembra che possa essere rifatto a un caso piuttosto chiaro di invio singolo.

In secondo luogo, sì, è possibile. Utilizzare System.Diagnostics.StackTrace per camminare nello stack; quindi ottenere l'appropriato StackFrame un livello in alto. Quindi determinare quale metodo era il chiamante utilizzando GetMethod() su quello StackFrame. Si noti che la creazione di una traccia di stack è un'operazione potenzialmente costosa ed è possibile che i chiamanti del proprio metodo oscurino da dove provengono realmente le cose.


Edit: Questo commento dall'OP rende abbastanza chiaro che questo probabilmente potrebbe essere un metodo generico o polimorfico. @devinb, potresti prendere in considerazione l'idea di creare una nuova domanda che fornisca maggiori dettagli su ciò che stai cercando di fare, e possiamo vedere se si presta bene a una buona soluzione.

La versione breve è che avrei finito hanno 30 o 40 funzioni identiche che erano semplicemente fuori da una o due linee. - devinb (12 secondi fa)

+0

D'oh! Cancellare la mia "risposta duplicata". . . stupide dita lente e stupide. . . +1 BTW –

+1

Niente sudore - Mi viene sempre il ninja da quel fottuto Jon Skeet. A proposito, non vorrei cancellare il tuo post a meno che non sia abbastanza vicino a un duplicato esatto. Se crei un punto che è per lo più simile al mio ma diverso in qualche modo, decisamente lo tengo in giro. –

0

C'è (quasi) sempre un design adeguato che può realizzare ciò che ti serve. Se fai un passo indietro per descrivere ciò che effettivamente devi fare, sono sicuro che otterrai almeno un buon design che non richiede di dover ricorrere a qualcosa di simile.

+0

Il mio design è un incubo molto strettamente correlato e autoreferenziale, quindi sì, ho ideato progetti che non avrebbero avuto questa bruttezza, ma hanno la loro bruttezza. Sto solo cercando di esplorare ciascuna opzione. – DevinB

+0

@devinb trovo altamente improbabile che le altre opzioni siano così brutte come questa –

+0

Sono più complicate. Al contrario di questo è relativamente semplice, ma orribile. = D Ero per lo più solo curioso. – DevinB

13

Come approccio alternativo, hai mai considerato di offrire una classe diversa in base al tipo di oggetto che richiede la classe.Di 'il seguente

public interface IC { 
    int DoSomething(); 
} 

public static CFactory { 
    public IC GetC(Type requestingType) { 
    if (requestingType == typeof(BadType1)) { 
     return new CForBadType1(); 
    } else if (requestingType == typeof(OtherType) { 
     return new CForOtherType(); 
    } 
    ... 
    } 
} 

Questo sarebbe un approccio molto più pulito rispetto a ogni metodo che modifica il suo comportamento in base all'oggetto chiamante. Separerebbe in modo pulito le preoccupazioni per le diverse implementazioni di IC. Inoltre, potrebbero tutti eseguire il proxy per l'implementazione C reale.

EDIT Esaminando lo stack di chiamate

Come molte altre persone indicate è possibile esaminare lo stack di chiamate per determinare quale oggetto è immediatamente chiamata la funzione. Tuttavia questo non è un modo infallibile per determinare se uno degli oggetti che vuoi utilizzare per un caso speciale ti sta chiamando. Ad esempio, potrei fare quanto segue per chiamarti da SomeBadObject ma rendere molto difficile per te determinare che l'ho fatto.

public class SomeBadObject { 
    public void CallCIndirectly(C obj) { 
    var ret = Helper.CallDoSomething(c); 
    } 
} 

public static class Helper { 
    public int CallDoSomething(C obj) { 
    return obj.DoSomething(); 
    } 
} 

Si potrebbe ovviamente camminare più indietro sullo stack di chiamate. Ma questo è ancora più fragile perché potrebbe essere un percorso completamente legale per SomeBadObject nello stack quando un oggetto diverso chiama DoSomething.

+0

Ciò richiederebbe ancora che A e B forniscano il loro tipo alla CFactory? Come impedire lo spoofing? – DevinB

+0

@devinb, puoi camminare sullo stack per prevenire lo spoofing ma non è un metodo infallibile. .Net non è stato progettato per una funzione per sapere che è il chiamante e di conseguenza qualsiasi tentativo di farlo funzionare avrà dei buchi. – JaredPar

+0

Sto provando a codificare con il paradigma che un codificatore malevolo avrà accesso alla mia fonte, ma non sarà in grado di cambiarlo. Ma penso che tu abbia ragione in quanto è quasi impossibile escludere tutte le possibilità "badObject" – DevinB

0

Bene, si potrebbe provare ad afferrare la traccia dello stack e determinare il tipo di chiamante da lì, il che è eccessivo a mio parere e sarebbe lento.

Che ne dici di un'interfaccia, che A,B implementerebbe?

interface IFoo { 
    int Value { get; } 
} 

E poi il tuo metodo di DoSomething sarebbe simile a questa:

public int DoSomething(int input, IFoo foo) 
    { 
     return input + foo.Value; 
    } 
0

È possibile utilizzare lo System.Diagnostics.StackTrace classe per creare una traccia dello stack. Quindi è possibile cercare lo StackFrame associato al chiamante. Lo StackFrame ha una proprietà Method che è possibile utilizzare per ottenere il tipo di chiamante.

Tuttavia, il metodo di cui sopra non deve essere utilizzato nel codice critico delle prestazioni.

0

È possibile esaminare lo stack di chiamate, ma è costoso e fragile. Quando il tuo codice è jit'ed il compilatore potrebbe inlineare i tuoi metodi così mentre potrebbe funzionare in modalità debug potresti ottenere uno stack diverso quando compilato in modalità di rilascio.

2

La risposta più semplice sarebbe passare nell'oggetto mittente come qualsiasi evento con il tipico mittente, la metodologia eventargs.

vostro codice chiamante sarebbe simile a questa:

return c.DoSomething(input, this); 

Il tuo metodo DoSomething sarebbe semplicemente controllare il tipo utilizzando l'operatore IS:

public static int DoSomething(int input, object source) 
{ 
    if(source is A) 
     return input + 1; 
    else if(source is B) 
     return input + 2; 
    else 
     throw new ApplicationException(); 

} 

Questo mi sembra qualcosa con un po 'più OOP. Potresti considerare C una classe astratta con un metodo e avere A, B ereditare da C e chiamare semplicemente il metodo. Ciò consentirebbe di controllare il tipo dell'oggetto base, che non è ovviamente falsificato.

Per curiosità, cosa stai provando con questo costrutto?

+0

In parole povere, C contiene informazioni per A e informazioni per B, e il metodo per accedere a tali informazioni è identico, ma B non può accedere (o essere ABILE per accedere) A informazioni e B non può accedere ad A informazioni. ... all'incirca. – DevinB

+1

Wow, questo mi ha ancora più confuso: P Puoi farne un esempio del mondo reale? Come un .. esempio di cani e gatti o qualcosa del genere? –

+1

Hmm ... forse. Un database contiene un elenco di spie americane e spie russe. Il metodo restituisce una lista. Quando gli americani chiedono, ottengono la lista americana, quando i russi chiedono, ottengono la lista russa. – DevinB

0

struttura come un gestore di eventi, sono sicuro poliziotto FX sarebbe anche suggerire di fare questo

static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) 
     { 
      throw new NotImplementedException(); 
     } 
+0

... Non capisco. – DevinB

+0

Sto dicendo di usare quello che suggeriva Matt Murrell, ma almeno usare la stessa convenzione degli eventi. passare il tuo oggetto chiamante, quindi il tuo argomento pari a – dfasdljkhfaskldjhfasklhf

2

non affidabile a causa della possibilità di inlining dal runtime.

+0

Assumi che queste funzioni siano troppo grandi per essere incorporate. – DevinB

0

potrei essere nei guai pensare avrei gestire in modo diverso, ma ....

Suppongo che questo:

classe A invita classe E per informazioni
chiamate classe C sulla classe E per informazioni
sia attraverso lo stesso metodo in e

che sai e creato tutte le tre classi e può nascondere il contenuto di classe e al pubblico. Se così non fosse, il tuo controller verrebbe sempre perso.

Logicamente: se è possibile nascondere il contenuto della classe E, lo si fa molto probabilmente distribuendo tramite una DLL.

Se si esegue questa operazione in ogni caso, perché non nascondere il contenuto delle classi A e C, nonché (stesso metodo), ma che consente A e C da utilizzare come base per le classi derivate B e D.

In classi A e C avresti un metodo per chiamare il metodo in classe E e fornire il risultato. In quel metodo (utilizzabile dalla classe derivata, ma il contenuto è nascosto agli utenti) puoi avere stringhe lunghe come "chiavi" passate al metodo nella classe E. Queste stringhe non potevano essere indovinate da nessun utente e sarebbero solo conosciute alla classi di base a, C ed E.

Avere le classi base incapsulano la conoscenza di come chiamare il metodo in modo corretto e sarebbe implementazione perfetta OOP Penso

Poi di nuovo ... potrei essere vista le complessità qui.

4

A partire da Visual Studio 2012 (.NET Framework 4.5) è possibile passare automaticamente caller information a un metodo utilizzando gli attributi del chiamante (VB e C#).

public void TheCaller() 
{ 
    SomeMethod(); 
} 

public void SomeMethod([CallerMemberName] string memberName = "") 
{ 
    Console.WriteLine(memberName); // ==> "TheCaller" 
} 

Gli attributi del chiamante si trovano nello spazio dei nomi System.Runtime.CompilerServices.


Questo è l'ideale per la realizzazione di INotifyPropertyChanged:

private void OnPropertyChanged([CallerMemberName]string caller = null) { 
    var handler = PropertyChanged; 
    if (handler != null) { 
     handler(this, new PropertyChangedEventArgs(caller)); 
    } 
} 

Esempio:

private string _name; 
public string Name 
{ 
    get { return _name; } 
    set 
    { 
     if (value != _name) { 
      _name = value; 
      OnPropertyChanged(); // Call without the optional parameter! 
     } 
    } 
} 
+0

+1 per indicare questo, perché lo scopo di aggiungere attributi di informazioni sul chiamante in 4.5 è un ottimo esempio di un contesto in cui questo è effettivamente necessario, anche se a prima vista potrebbe sembrare che viola i buoni principi di progettazione. In particolare, durante la scrittura di strumenti di traccia, debug o diagnostica. – BitMask777

+1

@ BitMask777: Sì, un altro esempio in cui ha senso è quando si implementa 'INotifyPropertyChanged'. –

Problemi correlati