Chamar um método estático em um parâmetro de tipo genérico

107

Eu esperava fazer algo assim, mas parece ser ilegal em C #:


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

Recebo um erro em tempo de compilação: "'T' é um 'parâmetro de tipo', que não é válido no contexto fornecido."

Dado um parâmetro de tipo genérico, como posso chamar um método estático na classe genérica? O método estático deve estar disponível, dada a restrição.

Remi Despres-Smyth
fonte

Respostas:

59

Nesse caso, você deve apenas chamar o método estático diretamente no tipo restrito. C # (e o CLR) não oferecem suporte a métodos estáticos virtuais. Assim:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

... não pode ser diferente de:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

Percorrer o parâmetro de tipo genérico é uma indireção desnecessária e, portanto, não é compatível.

JaredPar
fonte
25
Mas e se você mascarasse seu método estático em uma classe filha? public class SomeChildClass: SomeBaseClass {public new static StaticMethodOnSomeBaseClassThatReturnsCollection () {}} Você poderia fazer algo para acessar esse método estático de um tipo genérico?
Hugo Migneron
2
Confira a resposta de Joshua Pech abaixo, acredito que funcionaria nesse caso.
Remi Despres-Smyth
1
Funcionaria return SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection();? Se sim, você pode querer adicionar isso à sua resposta. Obrigado. Funcionou para mim No meu caso, minha classe tinha um parâmetro de tipo, então eu fiz return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();e funcionou.
toddmo
27

Para elaborar uma resposta anterior, acho que reflexão está mais perto do que você quer aqui. Eu poderia dar 1001 razões pelas quais você deve ou não fazer algo, responderei sua pergunta conforme solicitado. Eu acho que você deve chamar o método GetMethod no tipo do parâmetro genérico e partir daí. Por exemplo, para uma função:

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

Onde T é qualquer classe que tenha o método estático fetchAll ().

Sim, estou ciente de que isso é terrivelmente lento e pode falhar se someParent não forçar todas as suas classes filhas a implementar fetchAll, mas responder à pergunta conforme solicitado.

Joshua Pech
fonte
2
Não, de forma alguma. JaredPar acertou totalmente: T.StaticMethodOnSomeBaseClassThatReturnsCollection onde T: SomeBaseClass não é diferente de simplesmente declarar SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection.
Remi Despres-Smyth
2
Isso é o que eu precisava, funciona com um método estático
myro de
Essa era a resposta de que eu precisava porque não tinha controle das classes e da classe base.
Tim
8

A única maneira de chamar tal método seria por meio de reflexão. No entanto, parece que é possível envolver essa funcionalidade em uma interface e usar um padrão IoC / fábrica / etc baseado em instância.

Marc Gravell
fonte
5

Parece que você está tentando usar genéricos para contornar o fato de que não existem "métodos estáticos virtuais" em C #.

Infelizmente, isso não vai funcionar.

Brad Wilson
fonte
1
Não estou - estou trabalhando acima de uma camada DAL gerada. Todas as classes geradas herdam de uma classe base, que tem um método FetchAll estático. Estou tentando reduzir a duplicação de código em minha classe de repositório com uma classe de repositório "genérica" ​​- muitos códigos repetidos, exceto para a classe concreta usada.
Remi Despres-Smyth
1
Então por que você simplesmente não chama SomeBaseClass.StaticMethod ... ()?
Brad Wilson,
Desculpe, não me expliquei bem no comentário anterior. FetchAll é definido na base, mas implementado nas classes derivadas. Eu preciso chamá-lo na classe derivada.
Remi Despres-Smyth,
7
Se for um método estático, ele será definido e implementado pela base. Não existe método estático virtual / abstrato em C #, e nenhuma substituição para tal. Suspeito que você simplesmente o tenha declarado novamente, o que é muito diferente.
Marc Gravell
1
Sim, você está certo - eu fiz suposições inválidas aqui. Obrigado pela discussão, ajudou a me colocar no caminho certo.
Remi Despres-Smyth,
2

A partir de agora, você não pode. Você precisa de uma maneira de dizer ao compilador que T tem esse método e, atualmente, não há como fazer isso. (Muitos estão pressionando a Microsoft para expandir o que pode ser especificado em uma restrição genérica, então talvez isso seja possível no futuro).

James Curran
fonte
1
O problema é que, como os genéricos são fornecidos pelo tempo de execução, isso provavelmente significaria uma nova versão do CLR - que eles evitam (em grande parte) desde a 2.0. Talvez tenhamos um novo, no entanto ...
Marc Gravell
2

Aqui, eu posto um exemplo que funciona, é uma solução alternativa

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
fonte
2
Isso dá um erro de sintaxe para mim? O que isso public T : SomeBaseClasssignifica?
Eric
Se sua classe tem um método de instância someInstanceMethod (), você sempre pode chamá-lo fazendo (new T ()). SomeInstanceMethod (); - mas isso está chamando um método de instância - a pergunta feita como chamar um método estático da classe.
timóteo de
2

Eu só queria dizer que às vezes os delegados resolvem esses problemas, dependendo do contexto.

Se você precisar chamar o método estático como algum tipo de fábrica ou método de inicialização, então você pode declarar um delegado e passar o método estático para a fábrica genérica relevante ou o que quer que precise dessa "classe genérica com este método estático".

Por exemplo:

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

Infelizmente, você não pode garantir que a classe tenha o método certo, mas pode pelo menos garantir que o método de fábrica resultante tenha tudo o que espera (ou seja, um método de inicialização com a assinatura correta). Isso é melhor do que uma exceção de reflexão de tempo de execução.

Essa abordagem também tem alguns benefícios, ou seja, você pode reutilizar métodos init, fazer com que sejam métodos de instância, etc.

Amir Abiri
fonte
1

Você deve ser capaz de fazer isso usando reflexão, conforme descrito aqui

Devido ao link estar morto, encontrei os detalhes relevantes na máquina de retorno:

Suponha que você tenha uma classe com um método genérico estático:

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

Como você pode invocar este método usando relecção?

Acontece que é muito fácil ... É assim que você invoca um método genérico estático usando o Reflection:

// 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
fonte
O link está morto.
Necronomicron