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

时间:2009-03-10 07:16:55

标签: c# generics type-safety

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

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&lt; T&gt;。每个都有不同的类型,所以我不能真正将它们用作相同的类型。

我可以推导出Foo&lt; T&gt;来自FooBase,然后将它们全部视为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:

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

7 个答案:

答案 0 :(得分:4)

如何将变量作为参数传递给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的类型,而是检查你想要的类型。如果它可以为您提供该类型,那么它会抛出运行时异常。

答案 1 :(得分:3)

我认为这不起作用(给你想要的编译器错误)

你想要做什么:

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

这是编译错误吗?

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

无论如何,我认为“最佳方式”将是泛型和继承之间的妥协。您可以定义{F}的子类Foo<T>;那么你仍然可以拥有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; }
   }
}

答案 2 :(得分:1)

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

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

e.g。

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("#,###"); }
}

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

为什么不使用stringdoubleint


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

答案 5 :(得分:0)

实际上,这门课的目的是什么?最大的问题似乎是设计至少破坏了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
        }
    }
}
}

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

根据您的需要,您也可以尝试使用“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;
    }
}
}

答案 6 :(得分:0)

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

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

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