4

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

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

我可以从 FooBase 派生 Foo<T>,然后将它们全部视为 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

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

有任何想法吗?

编辑:

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

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

编辑2:

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

4

7 回答 7

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 持有什么类型,而是让 foo 检查你想要的类型。如果它可以为您提供该类型,那么它会提供,否则它会引发运行时异常。

于 2009-03-10T11:52:17.510 回答
3

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

你想要这个做什么:

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

那是编译器错误吗?

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

无论如何,我认为“最好的方法”将是泛型和继承之间的妥协。你可以定义一个Foo<T>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; }
   }
}
于 2009-03-10T07:26:14.717 回答
1

许多系统使用辅助方法来返回备用类型,就像 .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("#,###"); }
}
于 2009-03-10T07:51:07.997 回答
0

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

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

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

于 2009-03-10T07:43:29.857 回答
0

为什么不只使用string,doubleint


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

于 2009-03-10T07:46:38.240 回答
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;
    }
}
}
于 2009-03-10T08:52:13.543 回答
0

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

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

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

于 2009-03-10T10:58:46.640 回答