.NET: determina il tipo di classe "this" nel suo metodo statico


94

In un metodo non statico potrei usare this.GetType()e restituirebbe il file Type. Come posso ottenere lo stesso Typein un metodo statico? Ovviamente non posso scrivere solo typeof(ThisTypeName)perché ThisTypeNameè noto solo in runtime. Grazie!


16
Ti trovi in ​​un contesto STATICO e non puoi scrivere typeof (ThisTypeName)? Come?
Bruno Reis

1
Non c'è niente come 'runtime' all'interno di un metodo statico (supponendo che non si parli di un argomento passato a un metodo statico). In tal caso puoi semplicemente dire typeof (RelevantType).
Manish Basantani

2
Un metodo statico non può essere virtuale. Conosci già il tipo.
Hans Passant

7
Ci saranno molte classi derivate da una astratta. La classe astratta di base ha un dizionario statico <Int, Type>. Quindi le classi derivate si “registrano” nei costruttori statici (dic.Add (N, T)). E sì, conosco il tipo :) Sono solo un po 'pigro e non mi piace sostituire il testo e mi chiedevo se "T" può essere determinato in runtime. Per favore scusa la mia bugia, perché era necessario solo per semplificare la domanda. E ha funzionato;) Ora c'è una soluzione accettata. Grazie.
Yegor

Una sottoclasse eredita i metodi statici della sua superclasse, no? Non avrebbe senso che un metodo statico di una superclasse sia utile per tutte le sue sottoclassi? Statico significa semplicemente senza istanza, sicuramente il principio del codice comune in una classe base comune si applica ai metodi statici così come ai metodi di istanza?
Matt Connolly

Risposte:


134

Se stai cercando un 1 liner equivalente ai this.GetType()metodi statici, prova quanto segue.

Type t = MethodBase.GetCurrentMethod().DeclaringType

Sebbene questo sia probabilmente molto più costoso del semplice utilizzo typeof(TheTypeName).


1
Questo funziona bene. Grazie :) Non è così costoso perché sarà chiamato piuttosto raro.
Yegor

2
Entrase, per "costoso" Jared significa che sono costosi per il processore, di solito significa lenti. Ma ha detto, "molto più costoso" che significa più lento. Probabilmente non è affatto lento, a meno che tu non stia progettando un sistema di guida a razzo.
Dan Rosenstark

1
Ho visto GetCurrentMethod causare alcuni seri problemi di prestazioni. Ma poiché stai solo ottenendo il tipo, puoi metterlo nella cache.
Jonathan Allen

51
Restituisce sempre la classe che implementa il metodo corrente, non la classe su cui è stata chiamata nel caso delle sottoclassi.
Matt Connolly

3
Immagino sia utile evitare errori se il codice viene mai migrato a nomi di classi diversi o qualcosa del genere, ma un buon strumento di refactoring dovrebbe typeof(TheTypeName)comunque occuparsene .
Nyerguds

59

C'è qualcosa che le altre risposte non hanno del tutto chiarito e che è rilevante per la tua idea del tipo disponibile solo al momento dell'esecuzione.

Se si utilizza un tipo derivato per eseguire un membro statico, il nome del tipo reale viene omesso nel file binario. Quindi, ad esempio, compila questo codice:

UnicodeEncoding.GetEncoding(0);

Ora usa ildasm su di esso ... vedrai che la chiamata viene emessa in questo modo:

IL_0002:  call       class [mscorlib]System.Text.Encoding 
[mscorlib]System.Text.Encoding::GetEncoding(int32)

Il compilatore ha risolto la chiamata a Encoding.GetEncoding- non c'è traccia di UnicodeEncodingsinistra. Ciò rende la tua idea di "tipo attuale" priva di senso, temo.


24

Un'altra soluzione consiste nell'usare un tipo autoriferito

//My base class
//I add a type to my base class use that in the static method to check the type of the caller.
public class Parent<TSelfReferenceType>
{
    public static Type GetType()
    {
        return typeof(TSelfReferenceType);
    }
}

Quindi nella classe che lo eredita, creo un tipo autoreferenziale:

public class Child: Parent<Child>
{
}

Ora il tipo di chiamata typeof (TSelfReferenceType) all'interno di Parent otterrà e restituirà il tipo del chiamante senza la necessità di un'istanza.

Child.GetType();

-Rapinare


L'ho usato per i modelli singleton, cioè Singleton <T> ... i membri statici possono quindi fare riferimento a typeof (T) nei messaggi di errore o ovunque sia necessario.
yoyo

1
Ciao. Mi piace molto e apprezzo questa risposta perché mi dà una soluzione per trovare il tipo figlio da una funzione di base statica.
Bill Software Engineer

1
Ben fatto. Troppo triste che in C # ciò non possa essere fatto senza questa piccola duplicazione del codice, però.
Nome visualizzato

C'è un altro esempio di questa soluzione alternativa su stackoverflow.com/a/22532416/448568
Steven de Salas

6

Non puoi usarlo thisin un metodo statico, quindi non è possibile direttamente. Tuttavia, se hai bisogno del tipo di un oggetto, chiamalo GetTypee rendi l' thisistanza un parametro che devi passare, ad esempio:

public class Car {
  public static void Drive(Car c) {
    Console.WriteLine("Driving a {0}", c.GetType());
  }
}

Questo sembra un design scadente, però. Sei sicuro di aver davvero bisogno di ottenere il tipo di istanza stessa all'interno del proprio metodo statico? Sembra un po 'bizzarro. Perché non utilizzare semplicemente un metodo di istanza?

public class Car {
  public void Drive() { // Remove parameter; doesn't need to be static.
    Console.WriteLine("Driving a {0}", this.GetType());
  }
}

3

Non capisco perché non puoi usare typeof (ThisTypeName). Se questo è un tipo non generico, dovrebbe funzionare:

class Foo {
   static void Method1 () {
      Type t = typeof (Foo); // Can just hard code this
   }
}

Se è un tipo generico, allora:

class Foo<T> {
    static void Method1 () {
       Type t = typeof (Foo<T>);
    }
}

Mi manca qualcosa di ovvio qui?


7
Questo non funzionerà se crei una classe Bar derivata da Foo e poi la classe eredita Method1 - quindi una chiamata a Bar.Method1 elaborerà ancora typeof (Foo) che è sbagliato. Il Metodo1 ereditato dovrebbe in qualche modo sapere che è derviato in Bar, e quindi ottenere il tipo di (Bar).
JustAMartin

0

Quando il tuo membro è statico, saprai sempre di che tipo fa parte in fase di esecuzione. In questo caso:

class A
{
  public static int GetInt(){}

}
class B : A {}

Non puoi chiamare (modifica: a quanto pare, puoi, vedi il commento qui sotto, ma staresti comunque chiamando A):

B.GetInt();

poiché il membro è statico, non svolge alcun ruolo negli scenari di ereditarietà. Ergo, sai sempre che il tipo è A.


4
È possibile chiamare B.GetInt () - almeno, si potrebbe se non fosse privato - ma la compilazione sarà tradurlo in una chiamata a A.GetInt (). Provalo!
Jon Skeet

Nota sul commento di Jon: la visibilità predefinita in C # è privata, quindi il tuo esempio non funziona.
Dan Rosenstark

0

Per i miei scopi, mi piace l'idea di @T-moty. Anche se ho usato per anni informazioni di "tipo autoreferenziale", fare riferimento alla classe base è più difficile da fare in seguito.

Ad esempio (usando l'esempio di @Rob Leclerc dall'alto):

public class ChildA: Parent<ChildA>
{
}

public class ChildB: Parent<ChildB>
{
}

Lavorare con questo modello può essere impegnativo, per esempio; come si restituisce la classe base da una chiamata di funzione?

public Parent<???> GetParent() {}

O quando digiti il ​​casting?

var c = (Parent<???>) GetSomeParent();

Quindi, cerco di evitarlo quando posso e di usarlo quando devo. Se devi, ti suggerisco di seguire questo schema:

class BaseClass
{
    // All non-derived class methods goes here...

    // For example:
    public int Id { get; private set; }
    public string Name { get; private set; }
    public void Run() {}
}

class BaseClass<TSelfReferenceType> : BaseClass
{
    // All derived class methods goes here...

    // For example:
    public TSelfReferenceType Foo() {}
    public void Bar(TSelfRefenceType obj) {}
}

Ora puoi (più) facilmente lavorare con BaseClass. Tuttavia, ci sono momenti, come la mia situazione attuale, in cui non è necessario esporre la classe derivata, dall'interno della classe base, e usare il suggerimento di @ M-moty potrebbe essere l'approccio giusto.

Tuttavia, l'utilizzo del codice di @ M-moty funziona solo fintanto che la classe base non contiene alcun costruttore di istanza nello stack di chiamate. Purtroppo le mie classi base utilizzano costruttori di istanze.

Pertanto, ecco il mio metodo di estensione che tiene conto dei costruttori di "istanza" della classe base:

public static class TypeExtensions
{
    public static Type GetDrivedType(this Type type, int maxSearchDepth = 10)
    {
        if (maxSearchDepth < 0)
            throw new ArgumentOutOfRangeException(nameof(maxSearchDepth), "Must be greater than 0.");

        const int skipFrames = 2;  // Skip the call to self, skip the call to the static Ctor.
        var stack = new StackTrace();
        var maxCount = Math.Min(maxSearchDepth + skipFrames + 1, stack.FrameCount);
        var frame = skipFrames;

        // Skip all the base class 'instance' ctor calls. 
        //
        while (frame < maxCount)
        {
            var method = stack.GetFrame(frame).GetMethod();
            var declaringType = method.DeclaringType;

            if (type.IsAssignableFrom(declaringType))
                return declaringType;

            frame++;
        }

        return null;
    }
}

0

EDIT Questo metodo funziona solo quando distribuisci file PDB con l'eseguibile / libreria, come mi ha fatto notare markmnl .

Altrimenti sarà un grosso problema da rilevare: funziona bene nello sviluppo, ma forse non nella produzione.


Metodo di utilità, chiama semplicemente il metodo quando ne hai bisogno, da ogni punto del tuo codice:

public static Type GetType()
{
    var stack = new System.Diagnostics.StackTrace();

    if (stack.FrameCount < 2)
        return null;

    return (stack.GetFrame(1).GetMethod() as System.Reflection.MethodInfo).DeclaringType;
}

1
StackTrace è disponibile solo nelle build di debug
markmnl

Non corretto: StackTrace sarà disponibile quando distribuisci anche file .pdb in modalità di rilascio. stackoverflow.com/questions/2345957/...
T-moty

Ho capito il tuo punto. Non è accettabile che un metodo funzioni solo quando vengono distribuiti file PDB.
Modificherò
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.