C# 使用约束泛型只允许在父类型上运行
C# use constraint generics to allow function only on parent types
我正在尝试使用泛型约束来仅允许在另一种类型的父类型上调用泛型函数。
示例:
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent>()
where Parent : class
{
ParenthoodChecker<Derived, Parent> checker =
new ParenthoodChecker<Derived, Parent>();
}
}
public class ParenthoodChecker<Derived, Parent>
where Parent : class
where Derived : Parent
{
public ParenthoodChecker()
{
}
}
目前,我收到以下错误消息:
Error CS0311: The type 'Derived' cannot be used as type parameter
'Derived' in the generic type or method
'ParenthoodChecker'. There is no implicit reference
conversion from 'Derived' to 'Parent'. (CS0311)
有什么方法可以在编译时执行这样的事情吗?我不想在运行时进行检查,我觉得编译器应该能够推断出这一点。
您面临的问题是通用约束(where
关键字)仅与使用它的 class/method 的通用参数相关联。因此,在写泛型方法call<Parent>
时,只能在参数Parent
.
上定义约束
您可以通过向该方法添加一个新的人工泛型参数来解决该问题 - 这会使签名复杂化,但从语法的角度来看最终会使其正确:
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent, NewDerived>()
where Parent : class
where NewDerived: Derived, Parent
{
ParenthoodChecker<NewDerived, Parent> checker =
new ParenthoodChecker<NewDerived, Parent>();
}
}
我认为,除了丑陋和增加复杂性之外,此解决方案不会导致不正确的行为。 NewDerived
类型仍然是 Derived
.
在更高的理论基础上,这是比较值的问题:如果A > B
和A > C
,我们可以判断B > C
吗? - 显然不是,因为这里没有描述 B
和 C
之间的精确关系。
相反,如果你告诉Parent > NewDerived
和Derived > NewDerived
,就可以了。但是您仍然缺少 Parent > Derived
的证据。这就是为什么不可能(我认为)编写这样的函数让编译器认为 Parent
实际上是 Derived
.
的超类型的全部原因
通过上面给出的实现,您甚至可以使用 Derived
代替 NewDerived
来调用方法:
class A { }
class B : A { }
SomeClass<B> s = new SomeClass<B>();
s.call<A, B>();
在这个例子中,只有两个class,A
和B
,所以甚至没有其他class来扮演虚构 NewDerived
。整个操作保持在类型 A
(作为基础)和 B
(作为派生)之间。
编译器在这里与传递的实际类型名称混淆:
public class ParenthoodChecker<Derived, **Parent**>
where Parent : class
where Derived : Parent
{
public ParenthoodChecker()
{ }
}
我们通常会传递一个泛型类型并使用 where 子句指定类型的约束。
试试这个:
public class ParenthoodChecker<Derived, TParent>
where Derived : Parent
where TParent : class
{
public ParenthoodChecker()
{ }
}
考虑到 ParenthoodChecker
在我看来,这种在编译时的检查可以以更简单的方式完成:
public class Parent { }
public class Derived : Parent { }
public class NotDerived { }
Parent p1 = (Derived)null; // This will compile
Parent p2 = (NotDerived)null; // This won't compile
大多数答案都给出了部分答案,因此我将在此处汇总所有答案以及它们可能不适用于特定情况的原因。
选项 1:反转呼叫者和被呼叫者(m.rogalski 的评论)
public class SomeClass<Parent> //instead of derived
where Parent : class
{
public void call<Derived>()
where Derived : class, Parent
{
ParentChecker<Derived, Parent> checker = new ParentChecker<Derived, Parent>();
}
}
这是最接近完整编译时检查的,当然,这可能不可行,具体取决于系统设计(这是我的情况)。
选项 2:使用中间模板类型(Zoran Horvat 的回答)
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent, NewDerived>()
where Parent : class
where NewDerived: Derived, Parent
{
ParentChecker<NewDerived, Parent> checker =
new ParentChecker<NewDerived, Parent>();
}
}
只要您使用正确的输入(大多数情况下都是如此),它就可以工作,但使用编译时检查的原因主要是为了避免错过某些东西,所以我觉得这不是一个完整的答案.可用性比理想的要弱(您需要在调用时指定两种类型)并且它具有潜在的反例,例如:
public interface GrandP_A {}
public interface GrandP_B {}
public class Parent : GrandP_A {}
public class Child : Parent, GrandP_B {}
SomeClass<Parent> instance = new SomeClass<Parent>();
instance.call<GrandP_B, Child>();//this compiles
SomeClass 的目标是检查 SomeClass 的泛型类型参数是否派生自调用函数的第一个泛型类型。在这种情况下 GrandP_B。但是,GrandP_B 不是 Parent 的父 class,但调用仍然可以编译。再次说明,这仅在您使用得当时才有效(传递 Parent 作为 .call 的第二个通用类型)
选项 3:运行时检查
我不得不妥协,转而使用运行时检查。这显然不是编译时解决方案的方法,但它是唯一允许我想到的特定设计的方法。我仍然会在这里提到它作为部分答案,以防它在将来帮助某人。有关详细信息,请查看 this other answer 上的特定问题
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent>()
where Parent : class
{
if(typeof(Derived).IsSubclassOf(typeof(Parent)))
{
//do your stuff
}
else
{
throw new Exception("Must be called wih parent classes only!");
}
}
}
我正在尝试使用泛型约束来仅允许在另一种类型的父类型上调用泛型函数。
示例:
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent>()
where Parent : class
{
ParenthoodChecker<Derived, Parent> checker =
new ParenthoodChecker<Derived, Parent>();
}
}
public class ParenthoodChecker<Derived, Parent>
where Parent : class
where Derived : Parent
{
public ParenthoodChecker()
{
}
}
目前,我收到以下错误消息:
Error CS0311: The type 'Derived' cannot be used as type parameter 'Derived' in the generic type or method 'ParenthoodChecker'. There is no implicit reference conversion from 'Derived' to 'Parent'. (CS0311)
有什么方法可以在编译时执行这样的事情吗?我不想在运行时进行检查,我觉得编译器应该能够推断出这一点。
您面临的问题是通用约束(where
关键字)仅与使用它的 class/method 的通用参数相关联。因此,在写泛型方法call<Parent>
时,只能在参数Parent
.
您可以通过向该方法添加一个新的人工泛型参数来解决该问题 - 这会使签名复杂化,但从语法的角度来看最终会使其正确:
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent, NewDerived>()
where Parent : class
where NewDerived: Derived, Parent
{
ParenthoodChecker<NewDerived, Parent> checker =
new ParenthoodChecker<NewDerived, Parent>();
}
}
我认为,除了丑陋和增加复杂性之外,此解决方案不会导致不正确的行为。 NewDerived
类型仍然是 Derived
.
在更高的理论基础上,这是比较值的问题:如果A > B
和A > C
,我们可以判断B > C
吗? - 显然不是,因为这里没有描述 B
和 C
之间的精确关系。
相反,如果你告诉Parent > NewDerived
和Derived > NewDerived
,就可以了。但是您仍然缺少 Parent > Derived
的证据。这就是为什么不可能(我认为)编写这样的函数让编译器认为 Parent
实际上是 Derived
.
通过上面给出的实现,您甚至可以使用 Derived
代替 NewDerived
来调用方法:
class A { }
class B : A { }
SomeClass<B> s = new SomeClass<B>();
s.call<A, B>();
在这个例子中,只有两个class,A
和B
,所以甚至没有其他class来扮演虚构 NewDerived
。整个操作保持在类型 A
(作为基础)和 B
(作为派生)之间。
编译器在这里与传递的实际类型名称混淆:
public class ParenthoodChecker<Derived, **Parent**>
where Parent : class
where Derived : Parent
{
public ParenthoodChecker()
{ }
}
我们通常会传递一个泛型类型并使用 where 子句指定类型的约束。
试试这个:
public class ParenthoodChecker<Derived, TParent>
where Derived : Parent
where TParent : class
{
public ParenthoodChecker()
{ }
}
考虑到 ParenthoodChecker
在我看来,这种在编译时的检查可以以更简单的方式完成:
public class Parent { }
public class Derived : Parent { }
public class NotDerived { }
Parent p1 = (Derived)null; // This will compile
Parent p2 = (NotDerived)null; // This won't compile
大多数答案都给出了部分答案,因此我将在此处汇总所有答案以及它们可能不适用于特定情况的原因。
选项 1:反转呼叫者和被呼叫者(m.rogalski 的评论)
public class SomeClass<Parent> //instead of derived
where Parent : class
{
public void call<Derived>()
where Derived : class, Parent
{
ParentChecker<Derived, Parent> checker = new ParentChecker<Derived, Parent>();
}
}
这是最接近完整编译时检查的,当然,这可能不可行,具体取决于系统设计(这是我的情况)。
选项 2:使用中间模板类型(Zoran Horvat 的回答)
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent, NewDerived>()
where Parent : class
where NewDerived: Derived, Parent
{
ParentChecker<NewDerived, Parent> checker =
new ParentChecker<NewDerived, Parent>();
}
}
只要您使用正确的输入(大多数情况下都是如此),它就可以工作,但使用编译时检查的原因主要是为了避免错过某些东西,所以我觉得这不是一个完整的答案.可用性比理想的要弱(您需要在调用时指定两种类型)并且它具有潜在的反例,例如:
public interface GrandP_A {}
public interface GrandP_B {}
public class Parent : GrandP_A {}
public class Child : Parent, GrandP_B {}
SomeClass<Parent> instance = new SomeClass<Parent>();
instance.call<GrandP_B, Child>();//this compiles
SomeClass 的目标是检查 SomeClass 的泛型类型参数是否派生自调用函数的第一个泛型类型。在这种情况下 GrandP_B。但是,GrandP_B 不是 Parent 的父 class,但调用仍然可以编译。再次说明,这仅在您使用得当时才有效(传递 Parent 作为 .call 的第二个通用类型)
选项 3:运行时检查
我不得不妥协,转而使用运行时检查。这显然不是编译时解决方案的方法,但它是唯一允许我想到的特定设计的方法。我仍然会在这里提到它作为部分答案,以防它在将来帮助某人。有关详细信息,请查看 this other answer 上的特定问题
public class SomeClass<Derived>
where Derived : class
{
public void call<Parent>()
where Parent : class
{
if(typeof(Derived).IsSubclassOf(typeof(Parent)))
{
//do your stuff
}
else
{
throw new Exception("Must be called wih parent classes only!");
}
}
}