无法在类型参数化方法中将子 class 隐式转换为父 class
Cannot implicitly convert child class to parent class in type parametrized method
我最近不得不帮助解决某人使用通用方法 returning 时遇到的问题,虽然有多个问题需要解决,但我理解并可以解释所有问题 - 除了最后一个让编译器接受 return 类型的障碍。虽然我最终成功地编译了程序并且 运行 正确,但我仍然不能完全遵循 为什么 必须这样做的逻辑。
我在下面用一个最小的例子重现了这个问题。给定一个非常简单的父子 class 结构,只有一个泛型方法:
abstract class AbstractParentClass
{
public abstract T DoThing<T>() where T : AbstractParentClass;
}
class ConcreteChildClass : AbstractParentClass
{
public override T DoThing<T>()
{
return this;
}
}
这将导致 Cannot implicitly convert type 'ConcreteChildClass' to 'T'
的 return 行出现错误。有点奇怪,因为 T
被限制为 AbstractParentClass
的一个实例,而 this
是其中之一,但是没关系,当然,所以我们会明确地做到这一点:
public override T DoThing<T>()
{
return (T) this;
}
现在错误消息显示为 Cannot convert type 'ConcreteChildClass' to 'T'
。什么?如果 T
是不受约束的,那么当然,我知道我们不能保证它,但是像我们这样的继承,它肯定应该是一个简单的转换?
我们可以通过显式转换到父级来接近 class:
public override T DoThing<T>()
{
return (AbstractParentClass) this;
}
现在错误显示为 Cannot implicitly convert type 'AbstractParentClass' to 'T'. An explicit conversion exists (are you missing a cast?)
。为什么我们不能隐式转换为约束的确切类型 - 什么可能的情况会导致约束类型的 class 无法转换为...本身?但至少现在可以解决了,错误信息甚至直接告诉我们如何去做:
public override T DoThing<T>()
{
return (T)(AbstractParentClass) this;
}
现在一切 运行 都很好。如果需要,我们甚至可以让它看起来更漂亮:
public override T DoThing<T>()
{
return this as T;
}
在整个过程中,如果 T
不受约束,我当然理解为什么这些转换不可能像写的那样。但确实如此,并且编译器在某种程度上不应该有任何问题。出于某种原因,编译器是否只是不考虑允许的隐式转换的类型约束?根据我的经验,C# 中的所有此类情况背后都有一个很好的理由,我只是为了我的生活无法弄清楚这个。
如果有人对为什么编译器在处理这段(看似!)非常简单的代码时遇到问题有任何见解,我将不胜感激对允许这些转换的问题的解释。
因为从技术上讲你可以这样做:
class ConcreteChildClass2 : AbstractParentClass
{
public override T DoThing<T>()
{
return null;
}
}
var ccc = new ConcreteChildClass();
var ccc2 = ccc.DoThing<ConcreteChildClass2>();
即使你进行了转换,这也会在运行时爆炸:
System.InvalidCastException: Unable to cast object of type 'ConcreteChildClass' to type 'ConcreteChildClass2'.
如果您改为 return this as T
,您将得到 null
作为结果而不是转换异常。
换句话说,约束不保证 DoThing
returns 与定义 class 相同的类型,它只保证它 returns 某种类型继承 AbstractParentClass
.
“为什么”是即使 ConcreteChildClass
是一个 AbstractParentClass
并且 T
是一个 AbstractParentClass
,但这并不一定保证 ConcreteChildClass
是 T
.
对于real-world类比,Cat
是Pet
,Dog
是Pet
,但Cat
不是Dog
.
D Stanley 的回答显示了如何使用 T
的值调用 DoThing()
,这对于您正在处理的 object 类型来说是不正确的。
根据您的用例,您可以尝试使用其他一些模式来解决此问题。
例如,您可以在 parent class 上使用递归枚举声明类型,类似于 Java 对其枚举所做的操作:
abstract class AbstractParentClass<T> where T : AbstractParentClass<T>
{
public abstract T DoThing();
}
class ConcreteChildClass : AbstractParentClass<ConcreteChildClass>
{
public override ConcreteChildClass DoThing()
{
return this;
}
}
这使得您无法在调用 DoThing() 时告诉 ConcreteChildClass
使用不同的 class 作为其通用类型。您强制要求任何正确遵循该模式的 child class 都必须 return 是正确类型的实例。但是,它迫使您在任何地方传播通用类型:您不能只引用 AbstractParentClass
而不包含一些通用参数。您可以使用 non-generic 接口来解决这个问题,让代码使用它并不关心它给出的是哪种实现类型。但它仍然有点笨拙,因为(不像 Java 枚举)你不能真正强制每个实现扩展 parent class 和 使用自己作为通用类型。
我最近不得不帮助解决某人使用通用方法 returning 时遇到的问题,虽然有多个问题需要解决,但我理解并可以解释所有问题 - 除了最后一个让编译器接受 return 类型的障碍。虽然我最终成功地编译了程序并且 运行 正确,但我仍然不能完全遵循 为什么 必须这样做的逻辑。
我在下面用一个最小的例子重现了这个问题。给定一个非常简单的父子 class 结构,只有一个泛型方法:
abstract class AbstractParentClass
{
public abstract T DoThing<T>() where T : AbstractParentClass;
}
class ConcreteChildClass : AbstractParentClass
{
public override T DoThing<T>()
{
return this;
}
}
这将导致 Cannot implicitly convert type 'ConcreteChildClass' to 'T'
的 return 行出现错误。有点奇怪,因为 T
被限制为 AbstractParentClass
的一个实例,而 this
是其中之一,但是没关系,当然,所以我们会明确地做到这一点:
public override T DoThing<T>()
{
return (T) this;
}
现在错误消息显示为 Cannot convert type 'ConcreteChildClass' to 'T'
。什么?如果 T
是不受约束的,那么当然,我知道我们不能保证它,但是像我们这样的继承,它肯定应该是一个简单的转换?
我们可以通过显式转换到父级来接近 class:
public override T DoThing<T>()
{
return (AbstractParentClass) this;
}
现在错误显示为 Cannot implicitly convert type 'AbstractParentClass' to 'T'. An explicit conversion exists (are you missing a cast?)
。为什么我们不能隐式转换为约束的确切类型 - 什么可能的情况会导致约束类型的 class 无法转换为...本身?但至少现在可以解决了,错误信息甚至直接告诉我们如何去做:
public override T DoThing<T>()
{
return (T)(AbstractParentClass) this;
}
现在一切 运行 都很好。如果需要,我们甚至可以让它看起来更漂亮:
public override T DoThing<T>()
{
return this as T;
}
在整个过程中,如果 T
不受约束,我当然理解为什么这些转换不可能像写的那样。但确实如此,并且编译器在某种程度上不应该有任何问题。出于某种原因,编译器是否只是不考虑允许的隐式转换的类型约束?根据我的经验,C# 中的所有此类情况背后都有一个很好的理由,我只是为了我的生活无法弄清楚这个。
如果有人对为什么编译器在处理这段(看似!)非常简单的代码时遇到问题有任何见解,我将不胜感激对允许这些转换的问题的解释。
因为从技术上讲你可以这样做:
class ConcreteChildClass2 : AbstractParentClass
{
public override T DoThing<T>()
{
return null;
}
}
var ccc = new ConcreteChildClass();
var ccc2 = ccc.DoThing<ConcreteChildClass2>();
即使你进行了转换,这也会在运行时爆炸:
System.InvalidCastException: Unable to cast object of type 'ConcreteChildClass' to type 'ConcreteChildClass2'.
如果您改为 return this as T
,您将得到 null
作为结果而不是转换异常。
换句话说,约束不保证 DoThing
returns 与定义 class 相同的类型,它只保证它 returns 某种类型继承 AbstractParentClass
.
“为什么”是即使 ConcreteChildClass
是一个 AbstractParentClass
并且 T
是一个 AbstractParentClass
,但这并不一定保证 ConcreteChildClass
是 T
.
对于real-world类比,Cat
是Pet
,Dog
是Pet
,但Cat
不是Dog
.
D Stanley 的回答显示了如何使用 T
的值调用 DoThing()
,这对于您正在处理的 object 类型来说是不正确的。
根据您的用例,您可以尝试使用其他一些模式来解决此问题。
例如,您可以在 parent class 上使用递归枚举声明类型,类似于 Java 对其枚举所做的操作:
abstract class AbstractParentClass<T> where T : AbstractParentClass<T>
{
public abstract T DoThing();
}
class ConcreteChildClass : AbstractParentClass<ConcreteChildClass>
{
public override ConcreteChildClass DoThing()
{
return this;
}
}
这使得您无法在调用 DoThing() 时告诉 ConcreteChildClass
使用不同的 class 作为其通用类型。您强制要求任何正确遵循该模式的 child class 都必须 return 是正确类型的实例。但是,它迫使您在任何地方传播通用类型:您不能只引用 AbstractParentClass
而不包含一些通用参数。您可以使用 non-generic 接口来解决这个问题,让代码使用它并不关心它给出的是哪种实现类型。但它仍然有点笨拙,因为(不像 Java 枚举)你不能真正强制每个实现扩展 parent class 和 使用自己作为通用类型。