.NET: Określ typ „tej” klasy w jej metodzie statycznej

94

W niestatycznej metodzie, której mógłbym użyć this.GetType()i zwróciłoby to Type. Jak mogę uzyskać to samo Typew metodzie statycznej? Oczywiście nie mogę po prostu pisać, typeof(ThisTypeName)ponieważ ThisTypeNamejest znany tylko w środowisku wykonawczym. Dzięki!

Yegor
źródło
16
Jesteś w kontekście STATIC i nie możesz pisać typeof (ThisTypeName)? W jaki sposób?
Bruno Reis
1
W metodzie statycznej nie ma nic lepszego niż „środowisko uruchomieniowe” (zakładając, że nie mówisz o argumencie przekazywanym do metody statycznej). W takim przypadku możesz po prostu powiedzieć typeof (RelevantType).
Manish Basantani
2
Metoda statyczna nie może być wirtualna. Znasz już ten typ.
Hans Passant
7
Będzie wiele klas pochodnych z jednej abstrakcyjnej. Podstawowa klasa abstrakcyjna ma słownik statyczny <Int, Type>. Zatem klasy pochodne „rejestrują się” w statycznych konstruktorach (dic.Add (N, T)). I tak, znam ten typ :) Jestem trochę leniwy i nie lubię podmieniać tekstu i zastanawiałem się, czy „T” można określić w czasie wykonywania. Proszę wybaczyć moje kłamstwo, ponieważ wystarczyło uprościć pytanie. I zadziałało;) Jest teraz przyjęte rozwiązanie. Dzięki.
Jegor
Podklasa dziedziczy statyczne metody swojej nadklasy, prawda? Czy nie miałoby sensu, aby statyczna metoda nadklasy była użyteczna dla wszystkich jej podklas? Statyczny oznacza po prostu bez instancji, z pewnością zasada wspólnego kodu we wspólnej klasie bazowej ma zastosowanie zarówno do metod statycznych, jak i metod instancji?
Matt Connolly,

Odpowiedzi:

134

Jeśli szukasz 1 wkładki, która jest odpowiednikiem this.GetType()dla metod statycznych, wypróbuj następujące rozwiązania.

Type t = MethodBase.GetCurrentMethod().DeclaringType

Chociaż jest to prawdopodobnie znacznie droższe niż samo używanie typeof(TheTypeName).

JaredPar
źródło
1
Ten działa dobrze. Dzięki :) To nie jest takie drogie, ponieważ będzie nazywane dość rzadkim.
Jegor
2
Entrase, przez „drogie” Jareda, oznacza, że ​​są kosztowne dla procesora, co zwykle oznacza wolne. Powiedział jednak, że „znacznie droższe”, czyli wolniej. Prawdopodobnie wcale nie powolny, chyba że projektujesz system naprowadzania rakiety.
Dan Rosenstark
1
Widziałem, że GetCurrentMethod powoduje poważne problemy z wydajnością. Ale ponieważ otrzymujesz tylko typ, możesz go buforować.
Jonathan Allen
51
To zawsze zwraca klasę, która implementuje bieżącą metodę, a nie klasę, do której została wywołana w przypadku podklas.
Matt Connolly,
3
Wydaje mi się, że przydaje się unikanie błędów, jeśli kod zostanie kiedykolwiek przeniesiony do innych nazw klas lub coś w tym rodzaju, ale dobre narzędzie do refaktoryzacji powinno i typeof(TheTypeName)tak załatwić .
Nyerguds
59

Jest coś, czego inne odpowiedzi nie do końca wyjaśniły, a co jest istotne dla twojego pomysłu, który jest dostępny tylko w czasie wykonywania.

Jeśli używasz typu pochodnego do wykonania statycznego elementu członkowskiego, rzeczywista nazwa typu jest pomijana w pliku binarnym. Na przykład skompiluj ten kod:

UnicodeEncoding.GetEncoding(0);

Teraz użyj na nim ildasm ... zobaczysz, że wywołanie jest emitowane w następujący sposób:

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

Kompilator rozwiązał wywołanie Encoding.GetEncoding- nie ma już śladu UnicodeEncoding. Obawiam się, że przez to twój pomysł na „obecny typ” jest bezsensowny.

Jon Skeet
źródło
24

Innym rozwiązaniem jest użycie autoreferencyjnego typu

//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);
    }
}

Następnie w klasie, która go dziedziczy, tworzę typ odwołujący się do siebie:

public class Child: Parent<Child>
{
}

Teraz typ wywołania typeof (TSelfReferenceType) wewnątrz Parent pobierze i zwróci Type obiektu wywołującego bez potrzeby instancji.

Child.GetType();

-Obrabować

Rob Leclerc
źródło
Użyłem tego dla pojedynczych wzorców, tj. Singleton <T> ... statyczne składowe mogą następnie odwoływać się do typeof (T) w komunikatach o błędach lub gdziekolwiek jest to potrzebne.
jojo,
1
Cześć. Naprawdę podoba mi się i doceniam tę odpowiedź, ponieważ daje mi to obejście, aby znaleźć typ dziecka ze statycznej funkcji bazowej.
Bill Software Engineer
1
Niezłe. Szkoda jednak, że w C # nie można tego zrobić bez tego małego powielania kodu.
Nazwa wyświetlana
Jest inny przykład tego obejścia na stackoverflow.com/a/22532416/448568
Steven de Salas
5

Nie możesz użyć thisw metodzie statycznej, więc nie jest to możliwe bezpośrednio. Jeśli jednak potrzebujesz typu jakiegoś obiektu, po prostu wywołaj GetTypego i nadaj thisinstancji parametr, który musisz przekazać, np .:

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

Wygląda to jednak na kiepski projekt. Czy na pewno chcesz uzyskać typ samej instancji w jej własnej metodzie statycznej? Wydaje się to trochę dziwne. Dlaczego nie skorzystać po prostu z metody instancji?

public class Car {
  public void Drive() { // Remove parameter; doesn't need to be static.
    Console.WriteLine("Driving a {0}", this.GetType());
  }
}
John Feminella
źródło
3

Nie rozumiem, dlaczego nie możesz użyć typeof (ThisTypeName). Jeśli to nie jest typ ogólny, to powinno działać:

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

Jeśli jest to typ ogólny, to:

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

Czy brakuje mi czegoś oczywistego?

Tarydon
źródło
7
To nie zadziała, jeśli utworzysz klasę Bar pochodzącą z Foo, a następnie klasa odziedziczy Method1 - wtedy wywołanie Bar.Method1 nadal będzie przetwarzać typeof (Foo), który jest nieprawidłowy. Odziedziczona Method1 powinna w jakiś sposób wiedzieć, że jest dervied w Bar, a następnie otrzymać typeof (Bar).
JustAMartin
0

Kiedy Twój element członkowski jest statyczny, zawsze będziesz wiedział, jakiego typu jest częścią w czasie wykonywania. W tym przypadku:

class A
{
  public static int GetInt(){}

}
class B : A {}

Nie możesz zadzwonić (edytuj: najwyraźniej możesz, zobacz komentarz poniżej, ale nadal dzwoniłbyś do A):

B.GetInt();

ponieważ element członkowski jest statyczny, nie odgrywa roli w scenariuszach dziedziczenia. Ergo, zawsze wiesz, że typ to A.

Teun D
źródło
4
Państwo może wywołać B.GetInt () - co najmniej, można, jeśli nie były prywatne - ale kompilacji będzie przełożyć je na wezwanie do A.GetInt (). Spróbuj!
Jon Skeet,
Uwaga dotycząca komentarza Jona: domyślna widoczność w C # jest prywatna, dlatego Twój przykład nie działa.
Dan Rosenstark
0

Dla moich celów podoba mi się pomysł @ T-moty. Mimo że od lat korzystam z informacji o „typach odwołań do samych siebie”, późniejsze odniesienie do klasy bazowej jest trudniejsze.

Na przykład (używając przykładu @Rob Leclerc z góry):

public class ChildA: Parent<ChildA>
{
}

public class ChildB: Parent<ChildB>
{
}

Na przykład praca z tym wzorcem może być trudna; jak zwrócić klasę bazową z wywołania funkcji?

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

Albo podczas castingu?

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

Dlatego staram się tego unikać, kiedy mogę, i używać tego, kiedy muszę. Jeśli musisz, proponuję postępować zgodnie z tym wzorem:

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) {}
}

Teraz możesz (bardziej) łatwiej pracować z BaseClass. Jednak są chwile, tak jak moja obecna sytuacja, kiedy ujawnianie klasy pochodnej z klasy bazowej nie jest potrzebne i użycie sugestii @ M-moty może być właściwym podejściem.

Jednak użycie kodu @ M-moty działa tylko wtedy, gdy klasa bazowa nie zawiera żadnych konstruktorów instancji na stosie wywołań. Niestety moje klasy bazowe używają konstruktorów instancji.

Dlatego oto moja metoda rozszerzenia uwzględniająca konstruktory instancji klasy bazowej:

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;
    }
}
Kabuo
źródło
0

EDYTUJ Te metody będą działać tylko wtedy, gdy wdrażasz pliki PDB z plikiem wykonywalnym / biblioteką, jak wskazał mi markmnl .

W przeciwnym razie będzie poważny problem do wykrycia: działa dobrze w fazie rozwoju, ale może nie w produkcji.


Metoda narzędziowa, po prostu wywołaj ją, kiedy potrzebujesz, z każdego miejsca kodu:

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;
}
T-moty
źródło
1
StackTrace jest dostępny tylko w kompilacjach debugowania
markmnl
Niepoprawne: StackTrace będzie dostępne podczas wdrażania plików .pdb w trybie wydania. stackoverflow.com/questions/2345957/…
T-moty
Rozumiem co masz na myśli. Niedopuszczalne jest, aby metoda działała tylko wtedy, gdy wdrażane są pliki PDB.
Poprawię