C# - 泛型和继承问题

7

我在继承和泛型方面遇到了问题。 这是说明我的问题的代码:

namespace TestApplication
{
    public class MyClass<T>
    {
        private T field;

        public MyClass(T field)
        {
            this.field = field;
        }
    }

    public class MyIntClass : MyClass<int>
    {
        public MyIntClass(int field)
            : base(field)
        {
        }
    }
}

当我尝试像这样做:

MyClass<int> sth = new MyClass<int>(10);
MyIntClass intsth = (MyIntClass) sth;

我收到了一个类型转换异常:无法将'TestApplication.MyClass`1[System.Int32]'强制转换为'TestApplication.MyIntClass'。

此外,我无法创建强制转换运算符:

public static implicit operator MyIntClass(MyClass<int> myClass)

因为:'TestApplication.MyIntClass.implicit operator TestApplication.MyIntClass(TestApplication.MyClass)': 不允许从或向基类进行用户定义的转换

我需要像上面描述的那样创建转换。我不知道为什么我不能从一个基类类型进行转换。我该如何解决这个问题? 提前感谢您。

编辑

谢谢你们的回答。 现在我明白了,我不能从一个基类转换为派生类,而且我看到这与泛型无关。 但是为什么我不能从一个基类创建用户定义的转换呢?我有一个返回基类的方法。我能够定义一个转换方法,但我认为创建一个转换运算符会更好。

9个回答

12
如果对象实际上是派生类的类型,您才能将其从基类转换为派生类。我的意思是,您无法将基类(MyClass<int>)的实例转换为MyIntClass。但是,如果它实际上是存储为MyClass<int>实例的MyIntClass类型,则可以进行转换。
MyClass<int> foo = new MyIntClass();
MyIntClass bar = (MyIntClass)foo; // this works.

假设:

class Base {
   int x;
}

class Derived : Base {
   int y;
}

Base foo = new Base();
Derived bar = (Derived)foo;
如果允许的话,bar.y的值会是什么?实际上,从Derived转换到Base根本就不是一种转换。这只是告诉编译器让类型为Base的变量指向类型为Derived的对象。这是可能的,因为派生类具有更多或等于基类的特征,而反过来不成立。如果你能在基类和派生类之间创建一个转换运算符,C#编译器将无法将其与它们定义的内置关系区分开来。这就是为什么不能在继承层次结构中创建强制转换运算符的原因。

另外,我发现一个很好的思考方式是,.NET不知道您可能已经添加到MyIntClass的额外功能,但它确实知道MyIntClass始终可以转换回其基类。因此,只有一种转换方式可用。 - John_
假设你有一个水果基类和苹果和香蕉子类。如果一个水果变量的类型是苹果,我们如何期望将它强制转换为香蕉? - Mehrdad Afshari
在C# 4.0中,如果你愿意尝试,你终于可以比较苹果和梨了{} ;) - devio
但是为什么不允许从基类进行转换? - empi

10

到目前为止,其他答案都是正确的,但是我想指出你的例子与泛型没有任何关系。 这相当于:

using System;

class Base {}
class Child : Base {}

class Test
{
    static void Main()
    {
        Base b = new Base();

        // This will throw an exception
        Child c = (Child) b; 
    }
}

2
在评论中,您问道:
“为什么不允许从基类进行转换?”
简单来说,这是没有意义的。考虑下面的例子:
class BaseClass
{
    public int x;

    public BaseClass(int StartX)
    {
        this.x = StartX;
    }
}
class ChildClass: BaseClass
{
    public int Y;

    public BaseClass(int StartX, StartY): base(StartX)
    {
        this.y = StartY;
    }
}

class Program
{
    public static void Main()
    {
        BaseClass B = new BaseClass(3);
        ChildClass C = (ChildClass)B;
        Console.WriteLine(C.y);
    }
}

假设强制类型转换成功,你认为这个程序会输出什么?更糟糕的是-想象一下BaseClass有两个子类-ChildClassA和ChildClassB。你希望这样做吗?
ChildClassA A = new ChildClassA(); BaseClass bc = (BaseClass)A; ChildClassB B = (ChildClassB)bc;
这将有效地允许将ChildClassA实例转换为ChildClassB-完全错误。

2
我不想要默认的转换运算符。我想知道为什么我不能创建自己的转换运算符。 - empi

0
正如Mehrdad所述,您无法将对象向下转换。向上转换是隐式的,因此您无法覆盖它。
至于隐式操作符,您仍然可以在派生类中创建一个接收基类类型参数的构造函数。
如果您需要自由地进行强制转换,请将变量定义为基类,但实例化派生类。

0

正如所说,您正在尝试将一个对象转换为它不派生自的类型。也许您想要做这个:

MyClass<int> sth = new MyIntClass(10);
MyIntClass intsth = (MyIntClass) sth;

0

回答您的最后一次编辑。

这段代码已经编译,只是在运行时失败:

MyIntClass intsth = (MyIntClass) sth;

因此,如果明确指定,以下强制转换运算符将是多余的:

public static implicit operator MyIntClass(MyClass myClass)

因此,编译器应该阻止您添加该转换。我认为错误可能会令人困惑,但我认为它只是禁止将B类转换为A类(起初警告似乎防止任何转换为A)。

如果操作符是隐式的,那么它也是危险的,因为向下转换始终可能失败,所以您必须:

  1. 通过添加显式转换向编译器表明您知道这一点;
  2. 向读者(包括自己,几分钟后)表明该操作可能会失败。

0

如果您将基类分配/转换为派生类,则意味着将其视为按值复制的值。对于新手来说,C#的令人困惑之处在于它处理事情的方式不一致:

'int'是一个“简单”类型:

int i = 5;   // <- this creates an int.  
int j = i;    // <- this creates another int and copies the value of i into j.

'Object'不是一个简单类型:

Object a;                            // <- this does not create a copy of 'Object', only a reference to one
Object b = new Object();   // <- this actually creates an Object
a = b;                                 // <- this sets the reference to an object a to the reference to an object b.
                                          // both of them reference the same Object. No values were copied.

如果它正在复制值,那么将值从基类复制到派生类会起作用。但C#不像其他语言那样工作。
我认为这可能是让你感到困惑的原因。

0

不要创建一个MyIntClass,尝试使用别名:

using MyClass<int> = What.Ever.Namespace.MyIntClass;

这现在是有效的:

MyClass<int> foo = new MyClass<int>();
MyIntClass bar = (MyIntClass)foo;

理解一下,在使用 using 别名时,你必须在别名类型名称上限定你的命名空间(What.Ever.Namespace)。


如果您需要向MyIntClass添加方法,可以通过扩展方法来实现。 - user1228

0
关于您的第二个问题:
“但为什么我不能从基类创建用户定义转换?”
好吧,假设您有这种情况
class Base {
}

class Derived {
    public static operator Derived(Base b) { ... }
}

而你尝试着做这件事

Base x = new Derived();
Derived y = (Derived)x;

应该调用转换吗?当然不!x内部的值实际上是Derived类型,因此强制转换是直接的,没有转换。但是,如果值不是Derived类型,而是具体的Base类型,则必须进行用户定义的转换,否则我们将会有编译器错误。这一切都没有意义;用户定义的转换在编译时被发现,而x的值的类型只在运行时知道。因此,编译器不知道该怎么做——调用用户定义的转换还是简单地转换值...

希望这对您有点意义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接