接口(interface)中的C#静态函数

标签 c# directx

有没有办法在 C# 的接口(interface)中模拟静态函数?

我想将它用于工厂,其中每个对象都继承自 ICreateAble使用静态函数“创建”,然后在工厂类中调用 Factory.Create(Type t, string fromFile ) 调用 t.Create()

最佳答案

这个问题已经被问过几十次了,通常答案是否定的,从“你不能”到“你不应该”,或者“这没有意义”。 SO 和各种博客等上的许多有能力的开发人员都竭尽全力解释为什么此功能不适合 C# 语言。

但最重要的是,静态接口(interface)有有效的用例——我在 PHP 中经常使用它们,当然有一种方法可以在 C# 中实现类似的东西。你不需要反射(reflection)或黑客,你只需要稍微调整一下你的想法。

首先,考虑一下“静态”的确切含义:

public static class Foo
{
    static Foo()
    {
        Value = "test";
    }

    public static string Value { get; set; }
}

从某种意义上说,类 Foo 只是一个具有 Bar 属性的全局单例对象。该对象在启动时自动为您创建,并且您不能创建多个实例 - 但它在功能上等效于以下非静态类:
public class Bar
{
    public Bar()
    {
        Value = "test";
    }

    public string Value { get; set; }
}

您需要为构造函数使用不同的语法,并且属性访问和方法调用看起来不同 - 但是如果您选择仅保留此对象的一个​​全局实例,并且您选择在启动时创建该实例,则功能上没有区别.

这样做的目的是让您为以下想法做好准备:您的类型不需要是静态的。

您需要接口(interface)功能的原因是因为您需要指定类型需要遵守的“契约(Contract)” - 但接口(interface)对您不起作用,因为它们指定了对象需要如何遵守。

我对此的回答是,简单地将您的类型实现为具体对象并静态存储它们 - 而不是依赖于需要符合接口(interface)的类型部分的“静态”关键字。

例如,让我们考虑一个 Animal 类型,它有子类型 Cat 和 Dog - 并假设特定类型的所有动物都发出相同的声音,假设我们的动物类型必须提供声音。如您所知,以下方法不起作用:
public abstract class Animal
{
    public static abstract string Sound { get; }
}

public class Cat : Animal
{
    public static string Sound
    {
        get { return "Mee-oww."; }
    }
}

public class Dog : Animal
{
    public static string Sound
    {
        get { return "Woof!"; }
    }
}

public void Test()
{
    Animal cat = new Cat();
    Animal dog = new Dog();

    Assert.AreEqual(cat.GetType().Sound, Cat.Sound);
    Assert.AreEqual(dog.GetType().Sound, Dog.Sound);
}

除了static abstract不支持,测试的另一个严重问题是 cat.GetType()返回 System.Type ,这是类型定义本身的反射(reflect),而不是对将在启动时自动创建的全局静态对象的引用。由于没有抽象静态方法的语法,因此也没有静态调用此类方法的实现的语法。

访问静态 Sound 属性的唯一方法是直接引用类型,例如Cat.SoundDog.Sound .好的,所以这并不完全正确 - 您可以使用反射访问这些方法,但这可能不是很方便。当然,您还可以在每个 Animal 类型中为父类中的每个属性添加另一个非静态属性,显式访问静态属性。再说一次,如果你有很多动物类型,我认为这不是一种非常易于维护的方法......

让我们重新开始。

忘记尝试使用静态属性和标准类型来实现您想要的 - 相反,让我们添加一个具体类型来指定我们定义为动物类型的内容:
public class AnimalType
{
    public AnimalType(string sound)
    {
        Sound = sound;
    }

    public string Sound { get; private set; }
}

System.GetType()仅适用于系统类型,我们需要为 Animal-types 提供类似的工具,我们将不实现它 - 强制每个具体的 Animal-type 提供:
public abstract class Animal
{
    public abstract AnimalType AnimalType { get; }
}

现在我们可以实现具体的动物类型——因为我们想要一个 AnimalType 伴随扩展 Animal 类的每个系统类型,我们将在每个类型的静态字段中定义和存储 AnimalType 实例——以及我们的 AnimalType 属性的实现将返回该静态实例:
public class Dog : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Woof!");

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

public class Cat : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

我们现在可以编写一个工作测试:
public void StaticMethodInterface()
{
    Animal dog = new Dog();
    Animal cat = new Cat();

    Assert.AreEqual(dog.AnimalType.Sound, Dog.Type.Sound);
    Assert.AreEqual(cat.AnimalType.Sound, Cat.Type.Sound);
}

缺少的部分是一种工具,当我们只有 System.Type 但没有实际实例时,它使我们能够使用 Animal-types。换句话说,我们知道 Animal 的类型,我们需要访问它的 AnimalType。我想出了几个解决方案,一些涉及反射,一些需要动物类型的空构造函数。我最喜欢的解决方案是添加一个简单的注册表,将每个 Animal 系统类型映射到它匹配的 AnimalType:
public class AnimalType
{
    public AnimalType(string sound)
    {
        Sound = sound;
    }

    public string Sound { get; private set; }

    private static IDictionary<Type, AnimalType> _types = new Dictionary<Type, AnimalType>();

    public static void Register(Type type, AnimalType animalType)
    {
        _types.Add(type, animalType);
    }

    public static AnimalType Get(Type type)
    {
        return _types[type];
    }
}

我发现填充此注册表的最安全方法是为每个 Animal 系统类型添加静态构造函数,如下所示:
public class Dog : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Woof!");

    static Dog()
    {
        AnimalType.Register(typeof(Dog), Type);
    }

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

public class Cat : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");

    static Cat()
    {
        AnimalType.Register(typeof(Cat), Type);
    }

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

这确实需要一点纪律,就像涉及运行时类型相关任务的任何事情一样。我发现这种额外的工作比使用反射或空构造函数来填充注册表更可取。

最后,我们可以添加一个测试,演示如何在没有实例的情况下使用 System.Type 获取 AnimalType:
public void Test()
{
    var dogType = typeof (Dog);
    var catType = typeof (Cat);

    Assert.AreEqual(Dog.Type.Sound, AnimalType.Get(dogType).Sound);
    Assert.AreEqual(Cat.Type.Sound, AnimalType.Get(catType).Sound);
}

最后,这是完整的示例和测试:
public class AnimalType
{
    public AnimalType(string sound)
    {
        Sound = sound;
    }

    public string Sound { get; private set; }

    private static IDictionary<Type, AnimalType> _types = new Dictionary<Type, AnimalType>();

    public static void Register(Type type, AnimalType animalType)
    {
        _types.Add(type, animalType);
    }

    public static AnimalType Get(Type type)
    {
        return _types[type];
    }
}

public abstract class Animal
{
    public abstract AnimalType AnimalType { get; }
}

public class Dog : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Woof!");

    static Dog()
    {
        AnimalType.Register(typeof(Dog), Type);
    }

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

public class Cat : Animal
{
    public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");

    static Cat()
    {
        AnimalType.Register(typeof(Cat), Type);
    }

    override public AnimalType AnimalType
    {
        get { return Type; }
    }
}

public void Test()
{
    Animal dog = new Dog();
    Animal cat = new Cat();

    Assert.AreEqual(dog.AnimalType.Sound, Dog.Type.Sound);
    Assert.AreEqual(cat.AnimalType.Sound, Cat.Type.Sound);

    var dogType = typeof (Dog);
    var catType = typeof (Cat);

    Assert.AreEqual(Dog.Type.Sound, AnimalType.Get(dogType).Sound);
    Assert.AreEqual(Cat.Type.Sound, AnimalType.Get(catType).Sound);
}

请注意,在这个简单的演练中,我使用了一个 AnimalType,并为每个 Animal 系统类型注册了一个实例。假设您需要不同的 Animal-types 的替代实现 - 只需将 AnimalType 声明为抽象(或将其转换为接口(interface)),然后将其扩展为具体的 CatType 和 DogType 类型,改为注册它们。

最后,花点时间思考这样一个事实,即这实际上比静态接口(interface)灵活得多 - 从某种意义上说,您正在定义自己的“元类型”的特定于域的定义。好处是您可以使用继承和接口(interface)自由地对元类型建模,就像对任何其他类型层次结构建模一样。

我不认为这是一种解决方法 - 从某种意义上说,静态接口(interface)只是您已经可以做得更好的新语法。通过将您的元类型建模为具体的系统类型,您将获得一个更易于维护的代码库,无需进行大量重构即可扩展以满足新的需求;您只需像扩展任何其他类型一样扩展您的元类型。

简单地添加静态接口(interface)不会给你那种程度的灵活性。

关于接口(interface)中的C#静态函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5221689/

相关文章:

c# - Cosmos DB 在实现自定义 BotState 服务后抛出 "Resource Not Found"错误

c# - C#的lambda表达式语法是LALR(1)吗?

c++ - 如何在 DirectX11 中将纹理环绕方向从 U 更改为 V,反之亦然?

opengl - 任何人都可以解释 "field of view"

c++ - 错误 LNK2019 : unresolved external symbol _wWinMain@16 referenced in function ___tmainCRTStartup

c# - 在 Java 中使用 DirectX 进行快速屏幕捕获(从 C# 中的现有代码重写到 Java)

c# - 通用扩展方法的正确语法

c# - 在单个查询中从多个表中选择记录计数

c# - 是否可以将任意方法组作为参数传递给方法?

opengl - 确定内存中已编译着色器的大小