C#中的协方差和反方差
本文关键字:方差 | 更新日期: 2023-09-27 18:28:36
我首先要说的是,我是学习用C#编程的Java开发人员。因此,我会将我所知道的与我所学的进行比较。
我已经用C#泛型玩了几个小时了,我已经能够在C#中重现我在Java中所知道的相同内容,除了几个使用协方差和逆变的例子。我正在读的这本书的题目不太好。我当然会在网上寻求更多的信息,但在我这样做的同时,也许你可以帮助我找到以下Java代码的C#实现。
一个例子胜过千言万语,我希望通过寻找一个好的代码样本,我将能够更快地理解这一点。
协方差
在Java中,我可以做这样的事情:
public static double sum(List<? extends Number> numbers) {
double summation = 0.0;
for(Number number : numbers){
summation += number.doubleValue();
}
return summation;
}
我可以按如下方式使用此代码:
List<Integer> myInts = asList(1,2,3,4,5);
List<Double> myDoubles = asList(3.14, 5.5, 78.9);
List<Long> myLongs = asList(1L, 2L, 3L);
double result = 0.0;
result = sum(myInts);
result = sum(myDoubles)
result = sum(myLongs);
现在我确实发现,C#只在接口上支持协变/逆变,只要它们被明确声明为这样做(out/in)。我想我无法重现这种情况,因为我无法找到所有数字的共同祖先,但我相信,如果存在共同祖先,我本可以使用IEnumerable来实现这样的事情。由于IEnumerable是协变类型。正确的
对如何执行上面的列表有什么想法吗?只要给我指一个正确的方向。所有数字类型都有共同的祖先吗?
对比
我尝试的相反的例子如下。在Java中,我可以将一个列表复制到另一个列表中。
public static void copy(List<? extends Number> source, List<? super Number> destiny){
for(Number number : source) {
destiny.add(number);
}
}
然后我可以将它与反变体类型一起使用,如下所示:
List<Object> anything = new ArrayList<Object>();
List<Integer> myInts = asList(1,2,3,4,5);
copy(myInts, anything);
我的基本问题是,试图在C#中实现这一点,我找不到一个同时具有协变和逆变的接口,就像上面例子中List的情况一样。也许它可以用C#中的两个不同接口来完成。
对如何实现这一点有什么想法吗?
非常感谢大家提供的答案。我相信我会从你提供的任何例子中学到很多东西。
与其直接回答您的问题,不如回答一些略有不同的问题:
C#有办法对支持算术运算符的类型进行泛型吗?
不容易。如果有能力制作一个Sum<T>
方法,可以添加整数、二重、矩阵、复数、四元数。。。尽管这是一个经常被要求的功能,但它也是一个很大的功能,而且它在优先级列表中的位置从来都不足以证明它被包含在语言中。我个人很喜欢它,但你不应该期望它出现在C#5中。也许是在一个假设的未来版本的语言。
Java的"调用站点"协方差/反方差和C#的"声明站点"方差/反方差之间有什么区别?
实现级别的根本区别当然是,实际上,Java泛型是通过擦除实现的;尽管您可以获得泛型类型的愉快语法和编译时类型检查的好处,但您不一定能获得C#中的性能优势或运行时类型系统集成优势。
但这实际上更多的是一个实现细节。从我的角度来看,更有趣的区别是Java的方差规则是在本地强制执行的,而C#的方差规则则是在全局执行的。
也就是说:某些变体转换是危险的,因为它们意味着某些不类型安全的操作不会被编译器捕获。典型的例子是:
- 老虎是一种哺乳动物
- X的列表在X中是协变的。(假设。)
- 因此,老虎的列表就是哺乳动物的列表
- 哺乳动物列表中可以插入长颈鹿
- 因此,您可以将长颈鹿插入老虎列表中
这显然违反了类型安全,也违反了长颈鹿的安全。
C#和Java使用两种不同的技术来防止这种类型的安全冲突。C#说,当I<T>
接口被声明时,如果它被声明为协变,那么就不存在接受T的接口方法。如果没有将T插入列表的方法,那么你永远不会将长颈鹿插入老虎列表,因为没有将任何东西插入的方法。
相比之下,Java表示,在这个本地站点上,我们可以协变地处理类型,并承诺不会在这里调用任何可能违反类型安全的方法
我对Java的特性没有足够的经验来说明在什么情况下哪个"更好"。Java技术当然很有趣。
对于问题的第二部分,您不需要逆变,您只需要声明第一种类型可以转换为第二种类型。再次使用where TSource: TDest
语法来执行此操作。下面是一个完整的例子(展示了如何使用扩展方法):
static class ListCopy
{
public static void ListCopyToEnd<TSource, TDest>(this IList<TSource> sourceList, IList<TDest> destList)
where TSource : TDest // This lets us cast from TSource to TDest in the method.
{
foreach (TSource item in sourceList)
{
destList.Add(item);
}
}
}
class Program
{
static void Main(string[] args)
{
List<int> intList = new List<int> { 1, 2, 3 };
List<object> objList = new List<object>(); ;
ListCopy.ListCopyToEnd(intList, objList);
// ListCopyToEnd is an extension method
// This calls it for a second time on the same objList (copying values again).
intList.ListCopyToEnd(objList);
foreach (object obj in objList)
{
Console.WriteLine(obj);
}
Console.ReadLine();
}
您可以使用IConvertible
接口:
public static decimal sum<T>(IEnumerable<T> numbers) where T : IConvertible
{
decimal summation = 0.0m;
foreach(var number in numbers){
summation += number.ToDecimal(System.Globalization.CultureInfo.InvariantCulture);
}
return summation;
}
注意通用约束(where T : IConvertible
),它类似于Java中的extends
。
.NET中没有基本的Number
类
public static double sum(List<object> numbers) {
double summation = 0.0;
var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
foreach (var parsedNumber in parsedNumbers) {
summation += parsedNumber;
}
return summation;
}
如果列表中的任何对象不是数字并且没有实现IConvertible
,则必须捕获在Convert.ToDouble
期间发生的任何错误。
更新
不过,在这种情况下,我个人会使用IEnumerable
和泛型类型(而且,由于Paul Tyng,您可以强制T
实现IConvertible
):
public static double sum<T>(IEnumerable<T> numbers) where T : IConvertible {
double summation = 0.0;
var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
foreach (var parsedNumber in parsedNumbers) {
summation += parsedNumber;
}
return summation;
}