使用泛型调用C#接口的静态方法

32
有没有一种简单的方法来实现这个,如果可能的话不需要实例化一个对象:
interface I
{
     static  string GetClassName();
}

public class Helper
{

    static void PrintClassName<T>() where T : I
    {
         Console.WriteLine(T.GetClassName());
    }
}

你的接口实现会是什么样子? - dtb
2
鉴于接口定义实例成员,如果没有实例,您如何调用接口成员?如果您想要类名,请使用typeof(T)并转储接口。 - meandmycode
你在这里想要达到什么目的?确定typeof(T).FullName能完成任务吗? - Eric Smith
1
GetClassName 只是为了举例而已... - Toto
8个回答

25

可以尝试使用扩展方法:

public interface IMyInterface
{
     string GetClassName();
}

public static class IMyInterfaceExtensions
{
    public static void PrintClassName<T>( this T input ) 
        where T : IMyInterface
    {
         Console.WriteLine(input.GetClassName());
    }
}

这允许您添加静态扩展/实用方法,但仍需要您的IMyInterface实现的实例。

您不能为静态方法创建接口,因为这没有意义,它们是没有实例的实用方法,因此它们实际上没有类型。


好的,但是你认为我们能否避免输入参数。比如说我们可以使用接口的单例模式或者其他方式吗? - Toto
1
不确定您的意思-您会像实例化一个对象一样调用这个扩展方法:new MyImplementation()。PrintClassName()。 - Keith
2
除非您切换到使用反射(例如@Kent Boogaart的答案),否则您无法避免实例,但是那样您不应该真正使用泛型,因为您只是传递类型。 - Keith
2
非常感谢您的回答——这是一种针对特定接口添加“util”方法的绝妙方式。我还建议查看MSDN关于扩展方法的文档(下面有链接)。请注意,MSDN示例适当地为扩展方法命名空间,以便调用主体必须明确包含扩展。扩展方法:http://msdn.microsoft.com/en-us/library/bb383977.aspx - Brett

7
你无法继承静态方法。你的代码将无论如何都无法编译,因为接口不能有静态方法。
引用自littleguru的话:
在.NET中,继承只基于实例。静态方法在类型级别而非实例级别上定义。这就是为什么覆盖不适用于静态方法、属性和事件的原因...
静态方法在内存中只保留一次。没有为它们创建虚拟表等内容。
如果在.NET中调用实例方法,则始终会给出当前实例。 .NET运行时隐藏了这一点,但它确实发生了。每个实例方法作为第一个参数具有指向(引用)该方法所运行的对象的指针。这不会在静态方法中发生(因为它们在类型级别上定义)。编译器应该如何决定选择要调用的方法?

1
肯定的是编译器不能选择(或者如果它能够选择,那是因为我们明确调用了正确的静态函数与正确的类)。但是我们可以通过泛型来解决这个问题吗? - Toto
可能是有可能的。但是选择这种设计是为了保持更简单和清晰。 - Dykam
只是为了澄清,C#确实允许在接口上声明静态成员,然后需要实现者提供。BCL本身中这样一个接口的示例是IParsable<T>,它定义了一个静态的ParseTryParse方法。 - Mitch Denny
重要的是要注意,那时候情况并非如此。因此,重点应该放在“现在”。 - Dykam

3
如果您只需要类型名称,可以直接执行以下操作:
public class Helper
{
    static void PrintClassName<T>()
    {
         Console.WriteLine(typeof(T).Name);
    }
}

不错的回答(+1),但我想补充一点,如果你只是传递一个类型,你应该使用普通参数而不是这种方式:PrintClassName(Type input){Console.WriteLine(input.Name);} - Keith

3

2
在接口定义中声明一个静态属性、事件或方法不被视为合法定义。这是因为接口被认为是一种契约,代表每个客户端实例都会实现的内容。
静态声明基本上说明静态成员不需要物理客户端实现来执行所需功能,这与接口的一般概念相差甚远:提供经过验证的契约。

1
答案是“不完全是但有点像”。您可以为给定接口的所有实现者提供静态扩展方法,然后可以从您的实现者中的属性或另一个方法中调用此方法。例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace InterfacesWithGenerics
{
    class Program
    {
        static void Main(string[] args)
        {
            Helper.PrintClassName<Example>(new Example());
            Console.ReadLine();
        }
    }

    public class Example : I
    {
        #region I Members

        public string ClassName
        {
            get { return this.GetClassName(); }
        }

        #endregion
    }

    public interface I
    {
        string ClassName { get; }
    }

    public class Helper
    {

        public static void PrintClassName<T>(T input) where T : I
        {           
            Console.WriteLine( input.GetClassName()) ;
        }
    }

    public static class IExtensions
    {
        public static string GetClassName(this I yourInterface)
        {
            return yourInterface.GetType().ToString();
        }
    }
}

这里我们有一个接口(I),它定义了我们关心的属性,以及一个静态扩展方法(GetClassName),它适用于其类型的所有成员,执行获取所需信息的繁重工作。我们有一个类(Example),它实现了I接口,因此当我们调用静态帮助类并传入Example实例时,它会对其运行静态方法。不幸的是,在方法本身中直接引用类型T作为变量是无效的,您必须将实例传递到应用程序中。


0

你可以将className定义为特定类的属性。这是在.NET中存储元数据的首选方式。这样,您可以查询给定类的属性,而不需要实例。


0

是的,你可以 - 在某种程度上 - 如果你不介意定义新类型来代理实例调用静态方法:

虽然一个 interface 只能声明实例成员,但你可以使用 C# 的泛型的一些简单技巧,而不需要反射,来完成你想要的功能(而且不会采用 Java 风格的 AbstractFactoryBeanFactory 设计模式过度使用)。

我们可以做的是,定义一个单独的 struct(即值类型),其中包含调用我们想要的静态成员的实例成员。

所以如果我们有这个接口:

interface IStaticFunctionality
{
    void DoSomethingWithoutAnObjectInstance();
}

...我们想要做类似这样的事情:

void AGenericMethodThatDoesntHaveAnInstanceOfT<T>()
{
    T.DoSomethingWithoutAnObjectInstance();
}

如果这样,我们可以这样做:

void AGenericMethodThatDoesntHaveAnInstanceOfT<T>()
    where T : struct, IStaticFunctionality
{
    T t = default;
    t.DoSomethingWithoutAnObjectInstance();

    // Note the above code uses `T t default;` instead of `T t = new T()`.
    // This is because the C# compiler currently replaces `new T()` with `Activator.CreateInstance<T>()` in the generated bytecode.
    // This has poor performance compared to `default(T)` or a normal non-generic constructor call, but the compiler does this because it's a workaround for a design-bug back in C# 6.0: https://devblogs.microsoft.com/premier-developer/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/ 
}

所以,如果我们有不同类型的 static void DoSomethingWithoutAnObjectInstance 方法,我们只需要为每种类型定义 IStaticFunctionalitystruct 实现:

class Foo
{
    public static void DoSomethingWithoutAnObjectInstance()
    {
        Console.WriteLine("foo");
    }

    struct Static : IStaticFunctionality
    {
        void DoSomethingWithoutAnObjectInstance() => Foo.DoSomethingWithoutAnObjectInstance();
    }
}

class Bar
{
    public static void DoSomethingWithoutAnObjectInstance()
    {
        Console.WriteLine("bar");
    }

    struct Static : IStaticFunctionality
    {
        void DoSomethingWithoutAnObjectInstance() => Bar.DoSomethingWithoutAnObjectInstance();
    }
}

那么一个调用 AGenericMethodThatDoesntHaveAnInstanceOfT<Foo> 的调用点实际上看起来像:

AGenericMethodThatDoesntHaveAnInstanceOfT<Foo.Static>();

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