从Generics <t>转换为Specific SubClass </t>

时间:2010-01-08 14:53:16

标签: c# generics

我有一个班级

public class MyClass<T> where T : OneType
{
   T MyObj { get; set; }

   public MyCLass(T obj)
   {
   }
}

public class SubClass: MyClass<TwoType>
{
}

// snip for other similar class definition

其中,TwoType来自OneType

现在,我有这个实用方法

public static MyClass<T> Factory<T>(T vd)
 where T : OneType
{
   switch(vd.TypeName)
   {
      case Constant.TwoType
          return new SubClass((TwoType)vd);
     // snip for other type check
   }
}

显然,哪个函数会检查vd的类型,并创建一个合适的MyClass类型。唯一的问题是上面的代码不会编译,我不知道为什么

错误是

  

无法将T的表达式转换为TwoType

9 个答案:

答案 0 :(得分:18)

正如Grzenio正确指出的那样,T类型的表达式不能转换为TwoType。编译器不知道表达式是保证类型为TwoType - 由“if”语句保证,但编译器在分析类型时不考虑if语句的含义。相反,编译器假定T可以是满足约束的任何类型,包括ThreeType,一种派生自OneType但不是TwoType的类型。显然没有从ThreeType到TwoType的转换,因此也没有从T到TwoType的转换。

您可以通过说“好吧,将T视为对象,然后将对象强制转换为TwoType”来欺骗编译器。沿途的每一步都是合法的 - T可以转换为对象,并且可能存在从对象到TwoType的转换,因此编译器允许它。

然后,您将从SubClass转换为MyClass<T>时遇到同样的问题。同样,您可以先通过转换为对象来解决问题。

但是,这段代码仍然是错误的:

public static MyClass<T> Factory<T>(T vd) 
 where T:OneType 
{ 
   switch(vd.TypeName) 
   { 
      case Constant.TwoType 
       // WRONG
       return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd)); 
     // snip for other type check 
   } 
} 

为什么这是错的?好吧,考虑一下这里可能出错的一切。例如:你说

class AnotherTwoType : TwoType { }
...
x = Factory<TwoType>(new AnotherTwoType());

会发生什么?我们不调用SubClass构造函数,因为参数不完全是TwoType类型,它是从TwoType派生的类型。您可能想要

而不是开关
public static MyClass<T> Factory<T>(T vd) 
  where T:OneType 
{ 
  if (vd is TwoType)
       // STILL WRONG
       return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd)); 
  // snip for other type check 
} 

这仍然是错误的。再想想可能出现的问题:

x = Factory<OneType>(new TwoType());

现在会发生什么?参数是TwoType,我们创建一个新的SubClass,然后尝试将其转换为MyClass<OneType>,但是没有从SubClass到MyClass<OneType>的转换,所以这会在运行时崩溃并死掉。

您工厂的正确代码是

public static MyClass<T> Factory<T>(T vd) 
  where T:OneType 
{ 
  if (vd is TwoType && typeof(T) == typeof(TwoType))
       return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd)); 
  // snip for other type check 
} 

现在这一切都清楚了吗?你可以完全考虑一种不同的方法; 当你需要这么多强制转换和运行时类型检查来说服编译器代码是正确的时,证明整个事情都是一个糟糕的代码味道。

答案 1 :(得分:1)

它无法在.Net 3.5及更低版本中运行 - 对于任何T,SubClass都不是MyClass<T>类型,它只有MyClass<TwoType>类型。并且通用类不遵循其模板类型的继承,例如MyClass<string> 不是 MyClass<object>的子类 - 它们是C#中完全不同的类。

不幸的是,我不知道编写工厂方法的任何合理方法。

答案 2 :(得分:1)

太棒了,我通过编写代码来实现它:

return (new SubClass(vd as TwoType) as MyClass<T>);

return (MyClass<T>)(object)new SubClass((TwoType)(object)vd );

但是,

return (MyClass<T>)new SubClass((TwoType)vd );

不起作用。

as()投射似乎有所不同。

答案 3 :(得分:1)

我知道这是一个老问题,但我认为它也值得回答为什么这是不可能的(没有使用各种丑陋的解决方法)。

使用泛型时,从某种意义上说,您使用的是模板代码。编译器为您的代码使用了更严格的规则集,因为它必须在运行时可编译,最终版本将被编译。

因此,当您使用泛型创建类或方法时,它们必须可编译为任何可能的组合,这些组合遵守您对通用参数的约束设置的限制。

所以我进一步简化了你的代码以向你展示会发生什么:

我首先申报3个班级。父类和两个孩子:

public class Super { }

public class Child : Super { }

public class Sister : Super { }

然后我声明一个泛型方法,我尝试测试该类型,然后转换为子类型:

public void InvalidMethod<T>(T input)
  where T : Super
{
  Child castedReference = null;
  if (input is Child)
  {
    // This intuitively ought to be valid C#, but generates a compiletime error
    castedReference = (Child)input;
  }
  // Do stuff...
}

这会给你带来与原始问题相同的错误,但直观地说它看起来像固态代码。然而,实际发生的是,编译器检查代码是否可以在运行时在任何合法版本的方法中编译。这就是我的意思,你正在使用模板,因为在运行时,如果你用“Sister”作为你的类型参数调用方法,你会得到这个:

public void InvalidMethod(Sister input)
{
  Child castedReference = null;
  // Following 'if' is never true
  if (input is Child)
  {
    // Following statement is invalid C#
    castedReference = (Child)input;
  }
  // Do stuff...
}

因此我猜(我不确定,但如果我错了请纠正我),你在使用泛型时遇到的这种限制,是因为你的代码不应该破坏,只是因为你开始调用它在另一个上下文中,因此它们不允许您首先编写类似的代码,即使在编译时可能没有任何无效组合。

这就是帮助我理解为什么可以做某些事情,有些事情没有做到的事情。是的,您可以使用“as”而不是类型转换,因为如果转换无效,编译器只会为您提供“null”,但是通过显式类型转换,编译器会检查是否可行。对于泛型,它不是,以确保它也可以在运行时编译。

答案 4 :(得分:0)

更改工厂方法:

public static MyClass<T> Factory<T>(T vd)
    where T: OneType
{
    return new MyClass<T>(vd);
}

然后根本不需要开关。

答案 5 :(得分:0)

您的实用程序方法没有T的约束。

public static MyClass<T> Factory<T>(T vd) where T: OneType
{
    // ...
}

答案 6 :(得分:0)

你可以翻转设计吗?而不是创建一个需要知道OneType的每个子类的不灵活的工厂方法,而是向OneType添加一个看起来像这样的抽象方法;

public MyClass<OneType> GetMyClass();

TwoType负责创建SubClass个对象,ThreeType可以返回SubTypeThree等。

这里的线索是你正在根据对象的类型进行切换;这对于让子类完成工作总是很好的选择。

编辑1:示例

例如

public class TwoType: MyClass<TwoType>
{
  public override MyClass<OneType> GetMyClass()
  {
      return new SubClass(this);
  }
}

答案 7 :(得分:0)

这应该有效:

return (MyClass<T>)(object)new SubClass((TwoType)(object)vd);

答案 8 :(得分:0)

这对我有用:

public class OneType
{

}

public class MyClass<T> where T : OneType
{
    T MyObj
    { get; set; }
    public MyClass(T obj)
    {
    }
    public static MyClass<T> Factory<T>(T vd)
      where T : OneType
    {
        if (vd is TwoType)
        {
            return (MyClass<T>)(object)new SubClass(vd as TwoType);
        }
        return null;
    }

    public string Working
    {
        get { return this.GetType().Name; }
    }

}

public class TwoType : OneType
{

}


public class SubClass : MyClass<TwoType>
{
    public SubClass(TwoType obj)
        : base(obj)
    {

    }
}

在我的表格中我有这个:

        MyClass<TwoType> t = MyClass<TwoType>.Factory<TwoType>(new TwoType());

        MessageBox.Show(t.Working);
编辑:显然这不是理想的,正如埃里克指出的那样。虽然此代码在技术上编译并在某种程度上起作用,但您可能希望找到更好的整体解决方案。