【问题标题】:Best way to design a multi-type object设计多类型对象的最佳方法
【发布时间】:2009-03-10 19:52:17
【问题描述】:

假设我有一个数据对象,但这个对象可以保存多种类型的数据之一。

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; }

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

一种选择是使用泛型。

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

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

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

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

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

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

我也可以将值装箱并返回一个对象,但是没有类型安全性。

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

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

也许这甚至是不可能的......我必须做出一些妥协,但也许我错过了一些东西。

有什么想法吗?

编辑:

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

在这里做我想做的事情的最佳选择是什么?即,对所有 Foo 都一视同仁,但仍然有一个相对健全的访问机制?

EDIT2:

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

【问题讨论】:

  • 如果你的 Foo 实例可以是任何值,你的消费者如何知道它应该将结果存储在什么类型的变量中?
  • 我认为您试图在 C#(一种强类型语言)中获得类似于松散类型语言的行为,这是否正确?
  • 不,你不正确。我正在尝试使用强类型。这就是重点,否则我只会使用对象和演员。
  • 至于消费者如何知道什么类型,它不必知道。在对象使用的上下文中,只有一种有效类型,这也是他们将分配 Foo 值的类型。如果他们尝试使用无效类型,最好抛出编译时错误,但这是不可能的
  • 所以我可能不得不使用某种运行时异常,这不好,但我没有看到很多选择。问题是,这无论如何都不应该发生,而且会是一个明显的错误。

标签: c# generics type-safety


【解决方案1】:

将变量作为参数传递给 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 检查你想要的类型。如果它可以为您提供该类型,它会提供,否则它会引发运行时异常。

【讨论】:

  • 我认为这可能是这种情况下的最佳解决方案。我宁愿使用 out 参数。
【解决方案2】:

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

你想让它做什么:

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

这是编译器错误吗?

我认为“一视同仁”(至少按照您描述的方式)和“编译时错误”将是相互排斥的。

无论如何,我认为“最好的方法”将是泛型和继承之间的折衷。您可以定义一个Foo&lt;T&gt;,它是 Foo 的子类;那么你仍然可以收藏Foo

abstract public class Foo
{
  // Common implementation

  abstract public object ObjectValue { get; }
}

public class Foo<T> : Foo
{
   public Foo(T initialValue)
   {
      Value = initialValue;
   }

   public T Value { get; set; }

   public object ObjectValue
   { 
      get { return Value; }
   }
}

【讨论】:

  • 是的。我没有考虑过运行时的影响。好点子,谢谢。
  • @Mystere Man:无论哪种方式,您都必须使用它;根据定义,只是 Foo 的东西不知道它的值类型是什么;如果是这样,你为什么要使用基类? (而不是 type=specific 子类)
  • @Mystere Man:我想如果您需要,您可以让基类将值作为对象返回。
  • 是的,我在我的问题中提到了这一点。我也不喜欢那个选项。
  • 正如我在问题中所说,我想做的事情可能是不可能的。如果不是,我接受。我只是想在这里获得其他输入。
【解决方案3】:

许多系统使用辅助方法来返回备用类型,就像 .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("#,###"); }
}

【讨论】:

  • 这里的问题是我没有尝试将值转换为其他类型,而是尝试为该值返回正确的类型。如果是双精度,我不想将其转换为 int。
【解决方案4】:

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

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

我还看到了使用 Value 类来存储计算结果的实现,这些结果可以表示字符串、双精度、整数或布尔值。但是,问题是客户端代码必须检查枚举 {String, Integer, Double, Boolean} 的 Value.Type 属性,然后强制转换 Value.Value 属性(由 Value 类作为 Object 类型从外部公开) 或使用特定的 ValueString、ValueDouble、ValueInt、ValueBoolean getter。

【讨论】:

    【解决方案5】:

    为什么不直接使用stringdoubleint


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

    【讨论】:

    • 因为我正在尝试创建一个可以存储在集合中的对象(实际上是通过索引器使用),该集合表示对该对象有效的所有可能类型。
    • 那么使用对象呢?
    • 正如我在问题中所说,对象是一种可能的解决方案,但不是我喜欢的解决方案,因为铸造。另外,我不想将对象从一种类型转换或转换为另一种类型,只需返回数据的类型即可。
    • 另外,我并不想像 OfType 那样返回所有整数。我想返回一个特定的元素,让它成为数据的类型,或者更确切地说让它返回该类型的值。
    • 如果您首先使用 OfType,您可以使用 First 或其他方法将其缩小到您想要的值。在我看来,您希望您的代码知道您在想什么。这当然是不可能的......
    【解决方案6】:

    其实这个类的目的是什么?最大的问题似乎是设计至少打破了 SRP(单一责任原则)。

    不过,如果我没看错的话,您希望在容器中存储一些值,将容器传递给客户端并以类型安全的方式检索该值。

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

    namespace Project1 {
    public class Class1 {
        static int Main(string[] args) {
            Foo a = new Foo();
            a.SetValue(4);
            Console.WriteLine(a.GetValue<int>());
    
            Foo b = new Foo();
            a.SetValue("String");
            Console.WriteLine(a.GetValue<string>());
    
            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<T>() {
            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
            }
        }
    }
    }
    

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

    根据你的需要,你也可以试试“PHP in C#”:

    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;
        }
    }
    }
    

    【讨论】:

    • 不,我认为这不违反 SRP。数据用于相同的目的,所以这都是一个单一的责任。它只是提供不同的类型,我不希望将 int 数据转换为双精度数据(要处理 IEEE 的所有麻烦)或字符串。
    • 你的解决方案基本上都已经在我的问题中描述过了,由于各种原因被拒绝了。我可能不得不重新审视它们并做出一些妥协。
    • 好吧,问题是这个类的消费者真正想用这个值做什么。如果您可以提供任何使用该值的代码示例...
    【解决方案7】:

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

    这不是三种不同的类型 - 它是一种类型 - 双。您只需将非双精度返回值转换为双精度值,这就是温度(或者可能是浮点数)。

    一般来说,如果你对一件事有多个表示,那么它仍然是一件事,而不是多件事。将多种表示合二为一。

    【讨论】:

    • 本质上,这是数据库的一个字段(虽然它不是数据库)。有 100 个字段,每个字段可以保存 3 种数据中的一种,无法预测哪个是哪个(这来自我无法控制的外部服务)。我必须多态地处理这些值
    • 所以它们本质上是“相同的东西”,即使它们可以是不同的类型。我会这样设计“数据库”吗?不,但在这件事上我别无选择。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-05
    • 2011-08-24
    • 2011-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多