Wywołanie metody statycznej dla parametru typu ogólnego

107

Miałem nadzieję zrobić coś takiego, ale wydaje się to nielegalne w C #:


public Collection MethodThatFetchesSomething<T>()
    where T : SomeBaseClass
{
    return T.StaticMethodOnSomeBaseClassThatReturnsCollection();
}

Pojawia się błąd kompilacji: „„ T ”to„ parametr typu ”, który nie jest prawidłowy w danym kontekście”.

Biorąc pod uwagę parametr typu ogólnego, w jaki sposób mogę wywołać metodę statyczną w klasie ogólnej? Metoda statyczna musi być dostępna, biorąc pod uwagę ograniczenie.

Remi Despres-Smyth
źródło

Odpowiedzi:

59

W takim przypadku należy bezpośrednio wywołać metodę statyczną na typie z ograniczeniami. C # (i CLR) nie obsługują wirtualnych metod statycznych. Więc:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

... nie może różnić się od:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

Przechodzenie przez parametr typu ogólnego jest niepotrzebnym kierunkiem i dlatego nie jest obsługiwane.

JaredPar
źródło
25
Ale co by było, gdybyś zamaskował swoją statyczną metodę w klasie podrzędnej? public class SomeChildClass: SomeBaseClass {public new static StaticMethodOnSomeBaseClassThatReturnsCollection () {}} Czy możesz coś zrobić, aby uzyskać dostęp do tej metody statycznej z typu ogólnego?
Hugo Migneron
2
Sprawdź odpowiedź Joshuy Pecha poniżej, uważam, że to zadziała w tym przypadku.
Remi Despres-Smyth,
1
Czy return SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection();zadziała? Jeśli tak, możesz dodać to do swojej odpowiedzi. Dzięki. U mnie to zadziałało. W moim przypadku moja klasa miała parametr typu, więc tak zrobiłem return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();i to zadziałało.
toddmo
27

Aby rozwinąć poprzednią odpowiedź, myślę, że refleksja jest bliższa temu, czego chcesz tutaj. Mógłbym podać 1001 powodów, dla których powinieneś lub nie powinieneś coś robić, po prostu odpowiem na twoje pytanie. Myślę, że powinieneś wywołać metodę GetMethod na typie parametru generycznego i przejść stamtąd. Na przykład dla funkcji:

public void doSomething<T>() where T : someParent
{
    List<T> items=(List<T>)typeof(T).GetMethod("fetchAll").Invoke(null,new object[]{});
    //do something with items
}

Gdzie T to dowolna klasa, która ma statyczną metodę fetchAll ().

Tak, zdaję sobie sprawę, że jest to przerażająco wolne i może się zawiesić, jeśli someParent nie zmusi wszystkich swoich klas podrzędnych do implementacji funkcji fetchAll, ale odpowiada na zadane pytanie.

Joshua Pech
źródło
2
Nie, wcale. JaredPar ma absolutną rację: T.StaticMethodOnSomeBaseClassThatReturnsCollection, gdzie T: SomeBaseClass nie różni się niczym od zwykłego stwierdzenia SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection.
Remi Despres-Smyth,
2
To jest to, czego potrzebowałem, działa metodą statyczną
myro
To była odpowiedź, której potrzebowałem, ponieważ nie miałem kontroli nad klasami i klasą bazową.
Tim
8

Jedynym sposobem wywołania takiej metody byłaby refleksja, jednak wygląda na to, że możliwe jest zawinięcie tej funkcji w interfejs i użycie wzorca IoC / factory / etc opartego na instancji.

Marc Gravell
źródło
5

Wygląda na to, że próbujesz użyć typów ogólnych, aby obejść fakt, że w języku C # nie ma „wirtualnych metod statycznych”.

Niestety to nie zadziała.

Brad Wilson
źródło
1
Nie jestem - pracuję nad wygenerowaną warstwą DAL. Wszystkie wygenerowane klasy dziedziczą z klasy bazowej, która ma statyczną metodę FetchAll. Próbuję zredukować powielanie kodu w mojej klasie repozytorium za pomocą „ogólnej” klasy repozytorium - dużo powtarzającego się kodu, z wyjątkiem używanej konkretnej klasy.
Remi Despres-Smyth
1
W takim razie dlaczego po prostu nie wywołasz SomeBaseClass.StaticMethod ... ()?
Brad Wilson,
Przepraszam, nie wyjaśniłem się dobrze w poprzednim komentarzu. FetchAll jest zdefiniowana w bazie, ale zaimplementowana w klasach pochodnych. Muszę to wywołać w klasie pochodnej.
Remi Despres-Smyth
7
Jeśli jest to metoda statyczna, to jest zarówno definiowana, jak i implementowana przez bazę. Nie ma czegoś takiego jak wirtualna / abstrakcyjna metoda statyczna w C # i nie ma nadpisywania dla takich. Podejrzewam, że po prostu ponownie to zadeklarowałeś, co jest zupełnie inne.
Marc Gravell
1
Tak, masz rację - poczyniłem tu błędne założenia. Dzięki za dyskusję, pomogło mi to skierować mnie na właściwą ścieżkę.
Remi Despres-Smyth
2

Na razie nie możesz. Potrzebujesz sposobu, aby powiedzieć kompilatorowi, że T ma tę metodę, a obecnie nie ma sposobu, aby to zrobić. (Wielu naciska na Microsoft, aby rozszerzył to, co można określić w ogólnym ograniczeniu, więc być może będzie to możliwe w przyszłości).

James Curran
źródło
1
Problem polega na tym, że ponieważ środowisko wykonawcze dostarcza generyczne, oznaczałoby to prawdopodobnie nową wersję CLR - której (w dużej mierze) unikali od 2.0. Może mamy nowy, chociaż ...
Marc Gravell
2

Tutaj zamieszczam przykład, który działa, to obejście

public interface eInterface {
    void MethodOnSomeBaseClassThatReturnsCollection();
}

public T:SomeBaseClass, eInterface {

   public void MethodOnSomeBaseClassThatReturnsCollection() 
   { StaticMethodOnSomeBaseClassThatReturnsCollection() }

}

public Collection MethodThatFetchesSomething<T>() where T : SomeBaseClass, eInterface
{ 
   return ((eInterface)(new T()).StaticMethodOnSomeBaseClassThatReturnsCollection();
}
rodrijp
źródło
2
To daje mi błąd składni? Co to public T : SomeBaseClassznaczy?
Eric
Jeśli Twoja klasa ma metodę instancji someInstanceMethod (), zawsze możesz ją wywołać, wykonując (new T ()). SomeInstanceMethod (); - ale to jest wywołanie metody instancji - pojawiło się pytanie, jak wywołać statyczną metodę klasy.
Tymoteusza
2

Chciałem tylko wyrzucić, że czasami delegaci rozwiązują te problemy, w zależności od kontekstu.

Jeśli chcesz wywołać metodę statyczną jako rodzaj fabryki lub metody inicjującej, możesz zadeklarować delegata i przekazać metodę statyczną do odpowiedniej fabryki generycznej lub cokolwiek innego, co wymaga tej „klasy ogólnej z tą metodą statyczną”.

Na przykład:

class Factory<TProduct> where TProduct : new()
{
    public delegate void ProductInitializationMethod(TProduct newProduct);


    private ProductInitializationMethod m_ProductInitializationMethod;


    public Factory(ProductInitializationMethod p_ProductInitializationMethod)
    {
        m_ProductInitializationMethod = p_ProductInitializationMethod;
    }

    public TProduct CreateProduct()
    {
        var prod = new TProduct();
        m_ProductInitializationMethod(prod);
        return prod;
    }
}

class ProductA
{
    public static void InitializeProduct(ProductA newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class ProductB
{
    public static void InitializeProduct(ProductB newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class GenericAndDelegateTest
{
    public static void Main()
    {
        var factoryA = new Factory<ProductA>(ProductA.InitializeProduct);
        var factoryB = new Factory<ProductB>(ProductB.InitializeProduct);

        ProductA prodA = factoryA.CreateProduct();
        ProductB prodB = factoryB.CreateProduct();
    }
}

Niestety nie możesz wymusić, że klasa ma właściwą metodę, ale możesz przynajmniej wymusić w czasie kompilacji, że wynikowa metoda fabryczna ma wszystko, czego oczekuje (tj. Metodę inicjalizacji z dokładnie odpowiednim podpisem). Jest to lepsze niż wyjątek odbicia w czasie wykonywania.

Takie podejście ma również pewne zalety, np. Możesz ponownie użyć metod init, ustawić je jako metody instancji itp.

Amir Abiri
źródło
1

Powinieneś być w stanie to zrobić za pomocą refleksji, jak opisano tutaj

Ponieważ link nie działa, znalazłem odpowiednie szczegóły w maszynie zwrotnej:

Załóżmy, że masz klasę ze statyczną metodą ogólną:

class ClassWithGenericStaticMethod
{
    public static void PrintName<T>(string prefix) where T : class
    {
        Console.WriteLine(prefix + " " + typeof(T).FullName);
    }
}

Jak możesz przywołać tę metodę za pomocą odbicia?

Okazuje się, że jest to bardzo proste… Oto jak wywołać statyczną metodę ogólną za pomocą odbicia:

// Grabbing the type that has the static generic method
Type typeofClassWithGenericStaticMethod = typeof(ClassWithGenericStaticMethod);

// Grabbing the specific static method
MethodInfo methodInfo = typeofClassWithGenericStaticMethod.GetMethod("PrintName", System.Reflection.BindingFlags.Static | BindingFlags.Public);

// Binding the method info to generic arguments
Type[] genericArguments = new Type[] { typeof(Program) };
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

// Simply invoking the method and passing parameters
// The null parameter is the object to call the method from. Since the method is
// static, pass null.
object returnValue = genericMethodInfo.Invoke(null, new object[] { "hello" });
johnc
źródło
Link nie działa.
Necronomicron