接口中的C#静态功能

Ale*_*ger 7 c# directx

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

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

min*_*.dk 5

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

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

首先,考虑一下"静态"意味着什么:

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

    public static string Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

从某种意义上说,Foo类只是一个具有Bar属性的全局单例对象.在启动时自动为您创建此对象,并且您不能创建多个实例 - 但它在功能上等效于以下非静态类:

public class Bar
{
    public Bar()
    {
        Value = "test";
    }

    public string Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

这一点,是为您准备以下想法:您的类型不需要是静态的.

您需要接口功能的原因是因为您需要指定类型需要符合的"合同" - 但接口不适合您,因为它们指定了对象需要符合的方式.

我对此的回答是简单地将类型实现为具体对象并静态存储它们 - 而不是依赖于类型部分需要符合接口的"static"关键字.

例如,让我们考虑一个类型为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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

让我们重新开始.

忘记尝试使用静态属性和标准类型来实现你想要的东西 - 相反,让我们添加一个具体的类型来指定我们定义为动物类型:

public class AnimalType
{
    public AnimalType(string sound)
    {
        Sound = sound;
    }

    public string Sound { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

由于System.GetType()只适用于系统类型,我们需要一个类似于Animal类型的工具,我们将保留未实现的 - 强制每个具体的Animal类型提供:

public abstract class Animal
{
    public abstract AnimalType AnimalType { get; }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以实现具体类型的动物 - 因为我们想要一个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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们现在可以编写一个工作测试:

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);
}
Run Code Online (Sandbox Code Playgroud)

缺少的部分是一个工具,当我们拥有的是System.Type但没有实际的实例时,我们可以使用Animal类型.换句话说,我们知道Animal的类型,我们需要访问它的AnimalType.我想出了几个解决方案,一些涉及反射,一些需要Animal-types中的空构造函数.我最喜欢的解决方案是添加一个简单的注册表,将每个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];
    }
}
Run Code Online (Sandbox Code Playgroud)

我发现填充此注册表最安全的方法是向每个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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

最后,我们可以添加一个测试,演示当我们没有实例时如何使用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);
}
Run Code Online (Sandbox Code Playgroud)

最后,这是完整的示例和测试:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

我不认为这是一种解决方法 - 从某种意义上说,静态接口只是你可以做得更好的新语法.通过将元类型建模为具体的系统类型,您可以获得更易于维护的代码库,该代码库可以扩展以满足新的需求而无需重载重构; 您只需扩展元类型,就像扩展任何其他类型一样.

简单地添加静态接口不会给您这种灵活性.


Gre*_*mer 0

在“回答”之前,我想说的是,当你提出问题时,你应该将你的问题从你的解决方案中解脱出来,因​​为你很可能得到一个解决你的解决方案的答案,即使它不是解决你的根本问题的最佳方法。

如果您真正泛化了对象的创建,我会考虑稍微翻转一下这种模式并使用泛型来解决问题,而不是寻找一种模仿接口上静态方法的方法。当然,这需要重新考虑解决方案。

根据对象的性质(状态或行为),我还会考虑将创建与实现分开。如果对象是状态对象,则尤其如此。

我必须更多地了解您正在做的事情,但我假设您有一个调用 Create() 的抽象工厂,它充当一个具体工厂......在正在创建的对象的静态方法内。如果没有额外的上下文,我不确定这是一个好的模式。而且,我倾向于不。