【问题标题】:Cast from Generics<T> to Specific SubClass从泛型<T> 转换为特定子类
【发布时间】:2023-04-05 00:21:02
【问题描述】:

我有这样的课程

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

【问题讨论】:

  • 编译器返回什么错误?

标签: c# generics


【解决方案1】:

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

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

然后,您将遇到从 SubClass 转换为 MyClass&lt;T&gt; 的相同问题。同样,您可以通过先转换为对象来解决问题。

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

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&lt;OneType&gt;,但是没有从SubClass转换为MyClass&lt;OneType&gt;,所以这会在运行时崩溃死。

您工厂的正确代码是

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 
} 

现在一切都清楚了吗?您可能会考虑完全不同的方法; 当您需要如此多的强制转换和运行时类型检查来让编译器相信代码是正确的时,这就是整个事情都是糟糕的代码味道的证据。

【讨论】:

    【解决方案2】:

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

    在使用泛型时,从某种意义上说,您是在使用模板代码。编译器对您的代码使用一组更严格的规则,因为它必须在运行时也可编译,也就是编译最终版本的地方。

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

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

    我首先声明 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”,但是通过显式类型转换,编译器会检查是否可能。对于泛型,则不能确保它也可以在运行时编译。

    【讨论】:

      【解决方案3】:

      太棒了,我通过这样编写代码让它工作了:

      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() 的转换似乎有所不同。

      【讨论】:

        【解决方案4】:

        它在 .Net 3.5 及更低版本中不起作用 - 对于任何 T,子类都不是 MyClass&lt;T&gt; 类型,它只是 MyClass&lt;TwoType&gt; 类型。并且泛型类不遵循其模板类型的继承,例如MyClass&lt;string&gt; 不是 MyClass&lt;object&gt; 的子类 - 它们在 C# 中是完全不同的类。

        很遗憾,我不知道任何合理的方式来编写您的工厂方法。

        【讨论】:

        • 先转换成对象,欺骗编译器允许转换。
        • 它在 C# 4 中也不起作用。我们在泛型接口和委托上添加变体转换,但不在类上。
        【解决方案5】:

        改变你的工厂方法:

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

        那么你根本不需要开关。

        【讨论】:

        • 为什么这被否决了?这是可以编译的唯一答案(目前共有 7 个)。
        【解决方案6】:

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

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

        【讨论】:

        • 在阅读了我上面的答案(来自 Grzenio)之后,我记得遇到了同样的问题。我不认为你可以做你想做的事。至少,可能要等到 .Net 4.0?
        • 不,C# 4 并没有让这变得更好。只有接口和委托会有协变转换,类没有。
        【解决方案7】:

        这应该可行:

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

        【讨论】:

        • 它不起作用。消息是Cannot convert type 'ClassLibrary1.SubClass' to 'ClassLibrary1.MyClass&lt;T&gt;' D:\C#\Example1_9\ClassLibrary1\ClassLibrary1\Class1.cs
        • SubClass 不是MyClass&lt;T&gt;,这就是为什么
        • 你确定你没有错过(object)new SubClass(Myclass&lt;T&gt;) 之间的转换吗?
        【解决方案8】:

        你能翻转设计吗?与其创建一个需要了解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);
          }
        }
        

        【讨论】:

        • 问题是我需要根据传入的*Type创建MyClass
        • (编辑帖子以提供示例。)现在坚持。您没有名为 MyClass 的类型。你有无数种类型——MyClass、MyClass 等等。但这些是完全不同的类型。您只能有用地返回 MyClass
        【解决方案9】:

        这对我有用:

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

        编辑:显然,正如 Eric 在上面指出的那样,这并不理想。虽然此代码在技术上可以编译并在一定程度上工作,但您可能希望找到更好的整体解决方案。

        【讨论】:

          猜你喜欢
          • 2015-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多