C#如何创建可以不只一种类型的构造函数属性?

时间:2019-07-28 23:32:26

标签: c#

嗨,我很想知道如何在C#中创建一个构造函数属性,该属性可以是一种以上类型。

我看过堆栈溢出链接:

How to parse JSON string that can be one of two different strongly typed objects?

A variable of two types

...但是他们似乎与我的询问不符。

我可以按照下面的方式在python中生成我想要的东西。 IE初始化一个“ this_thing”类的对象,该对象可以使用“ thing”或“ thingy”类的对象。

等效的Python我想做的是:

class thing:
    def __init__(self, number):
        self.number = number
    @property
    def number(self):
        return self._number
    @number.setter
    def number(self, value):
        if not isinstance(value, int):
            raise TypeError('"number" must be an int')
        self._number = value

class thingy:
    def __init__(self, text):
        self.text= text
    @property
    def text(self):
        return self._text
    @text.setter
    def text(self, value):
        if not isinstance(value, str):
            raise TypeError('"text" must be a str')
        self._text = value

class this_thing:
    def __init__(self, chosen_thing, name_of_the_thing):
        self.chosen_thing = chosen_thing
        self.name_of_the_thing = name_of_the_thing 
    @property
    def chosen_thing(self):
        return self._chosen_thing
    @chosen_thing.setter
    def chosen_thing(self, value):
        if (not isinstance(value, (thing, thingy))):
            raise TypeError('"chosen_thing" must be a thing or thingy')
        self._chosen_thing = value

    @property
    def name_of_the_thing(self):
        return self._name_of_the_thing
    @name_of_the_thing.setter
    def name_of_the_thing(self, value):
        if not isinstance(value, str):
            raise TypeError('"name_of_the_thing" must be a str')
        self._name_of_the_thing = value

some_thing = thing(10)
another_thing = thingy("10")
new_thing = this_thing(some_thing, "Some Thing")
another_new_thing = this_thing(another_thing, "Another Thing")

在C#中,我有独立运行的“ Thing”和“ Thingy”类。 但是我想创建一个新类“ ThisThing”,该类可以使用“ Thing”或“ Thingy”类的对象,但是我不确定如何启用此操作。 IE声明/启用对象属性为多种类型。

namespace TheManyThings
{
    class ThisThing
    {
        public Thing | Thingy NewThing { get; set; }
        public string NameOfTheThing { get; set; }

        public ThisThing(Thing | Thingy newThing, string nameOfTheThing)
        {
            NewThing = newThing;
            NameOfTheThing = nameOfTheThing
        }
    }
}

1 个答案:

答案 0 :(得分:1)

理论上,您可以使用dynamic或类似object的弱类型在C#中复制动态类型的系统。

但是,就像在Python中一样,您将需要在运行时不断进行类型一致性检查

结果,您将失去像C#这样的静态类型化语言的大部分好处,并且还会受到SOLID OO社区的鄙视。

以下是您使用dynamic进行的原始Python代码的转换(请勿执行此操作)

public class Thing
{
    public Thing(int number) { Number = number; }
    public int Number { get; }
}

public class Thingy
{
    public Thingy(string text) { Text = text; }
    public string Text { get; }
}

public class ThisThing
{
    public ThisThing(dynamic chosenThing, string nameOfTheThing)
    {
        ChosenThing = chosenThing;
        NameOfTheThing = nameOfTheThing;
    }

    // Cache the accepted types
    private static readonly ICollection<Type> AcceptedTypes = new HashSet<Type> { typeof(Thing), typeof(Thingy) };
    private dynamic _chosenThing;
    public dynamic ChosenThing
    {
        get => _chosenThing;
        private set
        {
            if (!AcceptedTypes.Contains(value.GetType()))
            {
                throw new ArgumentException($"ChosenThing must be {string.Join(" or ", AcceptedTypes.Select(t => t.Name))}");
            }
            _chosenThing = value;
        }
    }

    public string NameOfTheThing { get; }
}

根据您的测试用例,可以执行以下操作:

var someThing = new Thing(10);
var anotherThing = new Thingy("10");
var newThing = new ThisThing(someThing, "Some Thing");
var anotherNewThing = new ThisThing(anotherThing, "Some Thing");

Console.WriteLine(newThing.ChosenThing.Number);
Console.WriteLine(anotherNewThing.ChosenThing.Text);

弱类型的问题是错误仅在运行时出现。 下面的内容将全部通过编译器(因为ChosenThingdynamic),但在运行时会崩溃。

var invalidThing = new ThisThing("Invalid for string types", "Invalid");
// newThing has a Number, not a Text property
Console.WriteLine(newThing.ChosenThing.Text);
// Vice Versa
Console.WriteLine(anotherNewThing.ChosenThing.Number);

使用通用界面的更好方法

一种更可接受的OO方法是为“可​​接受的”类型提供通用的基类或接口,然后改为使用它。这样,您将获得编译时类型的安全检查。

// Common interface
public interface IThing { }

public class Thing : IThing
{
    public Thing(int number) { Number = number; }
    public int Number { get; }
}

public class Thingy : IThing
{
    public Thingy(string text) { Text = text; }
    public string Text { get; }
}

由于有了公共接口,IThing现在可以用于约束传递给ThisThing的允许类型(即必须符合IThing合同),并且这些类型约束得到了强制执行在编译时:

public class ThisThing
{
    public ThisThing(IThing chosenThing, string nameOfTheThing)
    {
        ChosenThing = chosenThing;
        NameOfTheThing = nameOfTheThing;
    }

    public IThing ChosenThing { get; }

    public string NameOfTheThing { get; }
}

您现在将通过Thing合同公开ThingyIThing之间的所有常见功能。

就目前而言,该接口没有通用性,因此您需要将IThing下放到其中一个子类中,这又违反了SOLID Liskov Substitution Principal

Console.WriteLine(((Thing)newThing.ChosenThing).Number);
Console.WriteLine(((Thingy)anotherNewThing.ChosenThing).Text);

所以您真正想要的是抽象通用性,例如

public interface IThing 
{ 
     int CalculateValue();
}

现在ThingThingy都将被迫提供这种抽象的实现,然后接口的使用者现在可以安全地使用该接口,而无需对实际类型做任何进一步的假设具体实例:

Console.WriteLine(anyIThing.CalculateValue());