在泛型类型参数上调用静态方法

127
我希望能像这样做一些事情,但在C#中似乎是非法的:
public Collection MethodThatFetchesSomething<T>()
    where T : SomeBaseClass
{
    return T.StaticMethodOnSomeBaseClassThatReturnsCollection();
}

我遇到了一个编译时错误:

'T'是一个“类型参数”,在给定的上下文中无效。

如果有一个泛型类型参数,我该如何在泛型类上调用静态方法呢?这个静态方法必须是可用的,根据约束条件。


6
请参阅以下链接以获取更多关于此主题的信息:http://blogs.msdn.com/b/ericlippert/archive/2007/06/14/calling-static-methods-on-type-parameters-is-illegal-part-one.aspx,http://blogs.msdn.com/b/ericlippert/archive/2007/06/18/calling-static-methods-on-type-parameters-is-illegal-part-two.aspx和http://blogs.msdn.com/b/ericlippert/archive/2007/06/21/3445650.aspx。 - Eric Lippert
7
@EricLippert 上面评论中的链接已经失效。这些文章现在可以在以下链接找到:这里这里这里 - Bill Tür stands with Ukraine
@BillTür 谢谢!我最终会将它们转移到ericlippert.com,但这是一个缓慢的过程。 - Eric Lippert
10个回答

69

在这种情况下,您应该直接调用约束类型上的静态方法。C#(以及CLR)不支持虚拟静态方法。因此:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

...不可能与以下内容有任何不同:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

通过通用类型参数是不必要的间接方式,因此不被支持。


33
如果您在子类中遮蔽了静态方法,那该怎么办呢?public class SomeChildClass : SomeBaseClass{ public new static StaticMethodOnSomeBaseClassThatReturnsCollection(){} }您能做些什么来从泛型类型中访问该静态方法吗? - Hugo Migneron
2
请查看Joshua Pech在下面的答案,我相信那种情况下会起作用。 - Remi Despres-Smyth
1
return SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection(); 可以工作吗?如果可以的话,您可能需要将其添加到您的答案中。谢谢。这对我有用。在我的情况下,我的类有一个类型参数,所以我做了 return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();,它也起作用了。 - toddmo

38

对于之前的回答,我认为反射更适合这里的要求。我可以给出1001个关于是否应该做某件事情的理由,但我只会按照你的问题回答。我认为你应该在泛型参数的类型上调用GetMethod方法并从那里开始。例如,对于一个函数:

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

当 T 是任何具有静态方法 fetchAll() 的类时。

是的,我知道这很慢,如果 someParent 没有强制其所有子类实现 fetchAll,则可能会崩溃,但它回答了所提出的问题。


2
不,完全不是。JaredPar说得非常正确:T.StaticMethodOnSomeBaseClassThatReturnsCollection,其中T:SomeBaseClass并不比简单地声明SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection有什么不同。 - Remi Despres-Smyth
3
这正是我所需要的,它可以使用静态方法。 - myro
2
这是我需要的答案,因为我无法控制类和基类。 - Tim
3
如果您将硬编码的方法名称替换为以下内容:var methodeName = nameof(SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection);,则可以安全地重命名该方法(并获得编译器错误/使用自动重命名)。 - RoJaIt
2
@RemiDespres-Smyth 我不明白怎么做。如果 T 实现了一个不同的静态方法体呢?StaticMethodOnSomeBaseClassThatReturnsCollection 可以是一个带有默认返回值的静态方法的抽象类。T 可以覆盖它。那么解决方案显然非常不同。 - Shiv
如果有一种非反射的方式可以使用,那肯定会更好。反射似乎对于重构风险等来说太过于hacky了。我对这种不在编译时安全的东西过敏! - Shiv

13
你可以使用我所谓的代理单例,我已经用它作为一种“静态继承”使用了一段时间。
interface IFoo<T> where T : IFoo<T>, new()
{
    ICollection<T> ReturnsCollection();
}

static class Foo<T> where T : IFoo<T>, new()
{
    private static readonly T value = new();
    public static ICollection<T> ReturnsCollection() => value.ReturnsCollection();
}

// Use case

public ICollection<T> DoSomething<T>() where T : IFoo<T>, new()
{
    return Foo<T>.ReturnsCollection();
}

1
这个答案非常优秀,解决了我遇到的一个相当棘手的问题! - Andreas Pardeike
1
这个答案非常棒。它基本上创建了一个隐式对象缓存 T,用于获取实际的方法。 - Ryan
这个答案巧妙地“滥用”了某种不一致的规则,即您可以在基类中创建尚未知晓的泛型类型的新对象,并继承该基类 - 但是您无法访问该类型的已知静态字段(T.SomeFileld)。太棒了!虽然它似乎与派生类型中的静态构造函数不兼容(我在“private static readonly T value = new();”上得到“null reference exception”)。 - lisz

8

唯一调用这种方法的方式是通过反射,然而,似乎可以将该功能包装在接口中,并使用基于实例的IoC /工厂/等模式。


5

看起来你想使用泛型来解决 C# 中没有“虚拟静态方法”的问题。

不幸的是,这样做行不通。


1
我不是在直接操作数据访问层,而是在其上层进行工作。生成的类都继承自一个基类,该基类具有静态的FetchAll方法。我正在尝试通过一个“通用”存储库类来减少存储库类中的代码重复 - 除了使用的具体类之外,还有很多重复的代码。 - Remi Despres-Smyth
1
那你为什么不直接调用 SomeBaseClass.StaticMethod...() 呢? - Brad Wilson
抱歉,我在之前的评论中没有解释清楚。FetchAll 在基类上被定义,但是在派生类上被实现。我需要在派生类上调用它。 - Remi Despres-Smyth
7
如果它是一个静态方法,那么它既由基类定义又由其实现。在C#中没有虚拟/抽象静态方法,也没有这样的重写。我怀疑你只是重新声明了它,这是非常不同的。 - Marc Gravell
1
是的,你说得对 - 我在这里做出了无效的假设。感谢讨论,它帮助我找到了正确的方向。 - Remi Despres-Smyth

4

您应该能够使用反射来完成这项任务,具体方法可以参考这里

由于链接已经失效,我在wayback machine中找到了相关细节:

假设您有一个包含静态泛型方法的类:

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

你如何使用反射来调用这个方法呢?事实证明,这非常容易……以下是使用反射调用静态泛型方法的方法:
// 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" });

4
我只是想提一下,有时候委托可以解决这些问题,具体取决于上下文。
如果您需要将静态方法作为工厂或初始化方法调用,则可以声明一个委托,并将静态方法传递给相关的泛型工厂或任何需要这个“带有此静态方法的通用类”的东西。
例如:
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();
    }
}

不幸的是,您无法强制类具有正确的方法,但您至少可以在编译时强制执行生成的工厂方法具有其所需的所有内容(即具有完全正确签名的初始化方法)。这比运行时反射异常更好。

此方法还具有一些优点,例如您可以重用init方法,使它们成为实例方法等。


只是更新一下这个答案,因为我认为这似乎是最好的模式。 使用C# 8,您应该能够通过接口强制执行静态方法。 - Tony Topper
@TonyTopper我真的很希望使用C# 8的静态方法,我们能够使用像“where T:ISomeInterfaceWithStaticMethods”这样的约束来调用T.SomeStaticMethod(),但遗憾的是它仍然会给出“'T'是类型参数,在给定上下文中无效”的错误。我非常确定编译器在编译时有足够的信息来解析具体类型,所以它不起作用很奇怪。 - Ryan

2

目前你做不到。你需要一种告诉编译器T有该方法的方式,而目前还没有这样的方式。(很多人都在推动微软扩大泛型约束中可以指定的内容,所以也许将来会成为可能)。


1
问题在于,由于泛型是由运行时提供的,这可能意味着需要一个新的CLR版本 - 自2.0以来,他们(在很大程度上)避免了这种情况。也许我们该期待一个新版本了... - Marc Gravell

2
快进15年,在C# 11中,这现在是可能的!使用"static abstract"或"static virtual"(带有主体)声明您的接口方法,它们将直接从类型中访问,而不是实例。
public interface ICustomIntCollection
{
    static abstract Collection<int> Get();
    
    // or
    
    static virtual Collection<int> Get() => new List<int>();
}


public Collection<int> MethodThatFetchesSomething<T>()
    where T : SomeBaseClass, ICustomCollection
{
    return T.Get();
}

1
这里,我发布一个可用的示例,这是一个解决方法。
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();
}

2
这对我来说会产生语法错误?public T : SomeBaseClass是什么意思? - Eric
如果您的类有一个实例方法 someInstanceMethod(),则可以通过(new T()).someInstanceMethod()来随时调用它;但是这是调用实例方法,问题要求调用类的静态方法。 - timothy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接