设计多类型对象的最佳方法

假设我有一个数据对象,但是这个对象可以包含几种类型的数据之一。

class Foo { int intFoo; double doubleFoo; string stringFoo; } 

现在,我想创建一个访问器。 获取此数据的一些方法。 显然,我可以创建多个访问者:

 public int GetIntFoo(); public double GetDoubleFoo(); public string GetStringFoo(); 

或者我可以创建多个属性

 public int IntFoo { get; set; } public double DoubleFoo { get; set; } public string StringFoo { get; set; } 

我不认为这是一个非常好的设计。 它要求客户端代码更关心类型而不是它本来应该的。 更重要的是,我真的只需要这个类的单个值,上面的内容将允许同时分配每种类型中的一个。 不好。

一种选择是使用generics。

 class Foo { public T TheFoo { get; set; } } 

但是,这不会创建Foo,它会创建一个Foo 。 每种都有不同的类型,所以我不能真正使用它们作为相同的类型。

我可以从FooBase派生出Foo ,然后将它们全部视为FooBase,但后来我又回到了访问数据的问题。

一个不同的generics选项是使用这样的东西:

 class Foo { string stringRepresentationOfFoo; public T GetFoo() { return /* code to convert string to type */ } } 

当然问题是任何一种T都可以通过,坦率地说,它有点忙。

我也可以只插入值并返回一个对象,但是没有类型安全。

理想情况下,我想对所有Foo都一样,但我希望类型安全,这样如果没有StringFoo,我甚至无法编译对StringFoo的引用。

 Foo foo = new Foo("Foo"); string sFoo = foo.Value; // succeeds.   Foo foo = new Foo(0); int iFoo = foo.Value; // succeeds string sFoo = foo.Value; // compile error 

也许这甚至不可能……我将不得不做出一些妥协,但也许我错过了一些东西。

有任何想法吗?

编辑:

好吧,正如daniel指出的那样,运行时类型的编译时检查是不实际的。

做我想做的最好的选择是什么? 也就是说,对待所有的Foo是一样的,但仍然有一个相对理智的访问机制?

EDIT2:

我不想将值转换为不同的类型。 我想为值返回正确的类型。 也就是说,如果它是一个double,我不想返回一个int。

如何将变量作为参数传递给get? 像这样:

 int i = foo.get(i); 

然后在你的课堂上,你会有类似的东西:

 public int get(int p) { if(this.type != INTEGER) throw new RuntimeException("Data type mismatch"); return this.intVal; } public float get(float p) { if(this.type != FLOAT) throw new RuntimeException("Data type mismatch"); return this.floatVal; } 

这种类型的内部输出转换类型:而不是检查foo持有什么类型,你有foo检查你想要什么类型。 如果它可以为您提供该类型,那么它会抛出运行时exception。

我不认为这可行(给你你想要的编译器错误)

你想要做什么:

 Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1); int baz = bar.Value; 

这是编译器错误吗?

我认为“对待它们都是一样的”(至少按照你描述的方式)和“编译时错误”将是相互排斥的。

无论如何,我认为“最佳方式”将是generics和inheritance之间的妥协。 您可以定义Foo ,它是Foo的子类; 那么你仍然可以拥有Foo集合。

 abstract public class Foo { // Common implementation abstract public object ObjectValue { get; } } public class Foo : Foo { public Foo(T initialValue) { Value = initialValue; } public T Value { get; set; } public object ObjectValue { get { return Value; } } } 

许多系统使用辅助方法来返回备用类型,就像.net框架基础对象具有ToString()方法一样

选择哪个是您的每个对象的最佳基本类型,并为其他情况提供To方法

例如

 class Foo{ public Int32 Value { get; set; } public Byte ToByte() { return Convert.ToByte(Value); } public Double ToDouble() { return (Double)Value; } public new String ToString() { return Value.ToString("#,###"); } } 

一件事是将任何类型存储在类的内部状态中,另一种是在外部公开它。 当你写一个类时,你实际上是在为它的行为声明一个合同。 您编写它的方式将极大地影响客户端代码在使用该类时的外观。

例如,通过实现IConvertible接口,您可以声明您的类型可以转换为任何CLR类型作为等效值。

我还看到过使用Value类来存储计算结果的实现,结果可以表示字符串,double,int或boolean。 但是,问题是客户端代码必须检查枚举{String,Integer,Double,Boolean}的Value.Type属性,然后转换Value.Value属性(由Value类外部公开为Object类型) )或使用特定的ValueString,ValueDouble,ValueInt,ValueBoolean getters。

为什么不使用stringdoubleint


关于收集的信息:使用object怎么样? 无论如何,您将不得不检查类型等。 为了帮助您,您可以使用isas运算符。 和Enumerable.Cast方法 ,甚至更好的是Enumerable.OfType方法 。

实际上,这堂课的目的是什么? 最大的问题似乎是设计至少打破了SRP(单一责任原则)。

尽管如此,如果我正确地读它,你想在容器中存储一些值,将容器传递给客户端并键入 – 安全地检索值。

通过这种方法,您可以使用您的提案,即

 namespace Project1 { public class Class1 { static int Main(string[] args) { Foo a = new Foo(); a.SetValue(4); Console.WriteLine(a.GetValue()); Foo b = new Foo(); a.SetValue("String"); Console.WriteLine(a.GetValue()); Console.ReadLine(); return 0; } } class Foo { private object value; // watch out for boxing here! public void SetValue(object value) { this.value = value; } public T GetValue() { object val = this.value; if (val == null) { return default(T); } // or throw if you prefer try { return (T)val; } catch (Exception) { return default(T); // cast failed, return default(T) or throw } } } } 

但是,在这种情况下,为什么不简单地将数据作为对象传递并自己投射?

根据您的需要,您也可以尝试“在C#中使用PHP”:

 namespace Project1 { public class Class1 { static int Main(string[] args) { MyInt a = 1; MyInt b = "2"; Console.WriteLine(a + b); // writes 3 Console.ReadLine(); return 0; } } class MyInt { private int value; public static implicit operator int(MyInt container) { return container.value; } public static implicit operator MyInt(int value) { MyInt myInt = new MyInt(); myInt.value = value; return myInt ; } public static implicit operator MyInt(string stringedInt) { MyInt myInt = new MyInt(); myInt.value = int.Parse(stringedInt); return myInt; } } } 

对不起,我只是不买你的前提。 如果数据都具有相同的目的,那么它们应该都具有相同的类型。 考虑一个意味着保持当前温度的类,由几个Web服务之一返回。 所有服务都以摄氏温度返回。 但是一个返回一个int,一个返回一个double,一个返回一个字符串。

它不是三种不同的类型 – 它是一种类型 – 双重。 您只需要将非双返回转换为double,即温度(或浮点数)。

一般来说,如果你有一件事的多个表示,那么它仍然是一件事,而不是多件事。 将多个表示转换为一个。