【问题标题】:How to simulate Java generic wildcards in C#如何在 C# 中模拟 Java 通用通配符
【发布时间】:2018-03-27 15:20:16
【问题描述】:

以下 Java 代码比较两个数组的平均值,一个是整数,一个是双精度数。

class Generic_Class<T extends Number>
{
    T[] nums; // array of Number or subclass

    Generic_Class(T[] o)
    {
        nums = o;
    }

    // Return type double in all cases.
    double average()
    {
        double sum = 0.0;

        for(int i=0; i < nums.length; i++)
            sum += nums[i].doubleValue();

        return sum / nums.length;
    }


//  boolean sameAvg(Generic_Class<T> ob)
//  Using Generic_Class<T> i get the error:
//  incompatible types: Generic_Class<Double> cannot be converted to Generic_Class<Integer>

//  Using wilcards I get no error
    boolean sameAvg(Generic_Class<?> ob)
    {
        if(average() == ob.average())
            return true;
        return false;
    }
}

主要方法是这样的:

public static void main(String args[])
{
    Integer inums[] = { 1, 2, 3, 4, 5 };
    Double  dnums[] = { 1.0, 2.0, 3.0, 4.0, 5.0 };

    Generic_Class<Integer> iob = new Generic_Class<Integer>(inums);
    Generic_Class<Double>  dob = new Generic_Class<Double>(dnums);

    System.out.println("iob average is " + iob.average());
    System.out.println("dob average is " + dob.average());

    if (iob.sameAvg(dob))
        System.out.println("Averages of iob and dob are the same.");
    else
        System.out.println("Averages of iob and dob differ.");
}

结果是:

iob average is 3.0
dob average is 3.0
Averages of iob and dob are the same.

我尝试在 C# 中做同样的事情,但由于我没有通配符,我无法完成同样的任务。

如何使用 C# 做同样的事情?

谢谢。

【问题讨论】:

  • 这看起来不像 c#。是 Java 吗?
  • @ZoharPeled,是的,它是 Java。几秒钟前我已经编辑了代码。
  • 我已经编辑了您的标题并添加了 java 标签,以使您的问题更清楚。
  • 你没有展示你的 C# 代码,或者具体描述了它是如何不工作的。
  • sameAvg 代码可以简化为return average() == ob.average();

标签: java c# generics wildcard


【解决方案1】:

正如其他回答者所说,C# 中没有 Number 的等价物。你能得到的最好的是struct, IConvertible。但是,还有另一种方法可以使用通用通配符。

只需使用另一个通用参数:

public class Generic_Class<T> where T : struct, IConvertible
{
    T[] nums;
    public Generic_Class(T[] o)
    {
        nums = o;
    }

    public double Average()
    {
        double sum = 0.0;
        for(int i=0; i < nums.Length; i++)
            sum += nums[i].ToDouble(null);
        return sum / nums.Length;
    }

    // this is the important bit
    public bool SameAvg<U>(Generic_Class<U> ob) where U : struct, IConvertible
    {
        if(Average() == ob.Average())
            return true;
        return false;
    }
}

【讨论】:

  • 我也在想同样的事情并在这里实现了一个小提琴dotnetfiddle.net/AAMngv
  • 50 分钟前我也是这么想的 ;)
  • 详细说明:Java 版本是对此的简写,因为通配符隐含地包含类型参数边界。 Generic_Class&lt;?&gt; 等价于Generic_Class&lt;? extends Number&gt;,该方法参数本质上是&lt;U extends Number&gt; boolean sameAvg(Generic_Class&lt;U&gt; ob) 的简写,类似于C# 版本。
  • 我已将 where U : struct, IConvertible 更改为 where U : IConvertible 并且可以正常工作。为什么我们需要 struct 约束?
  • @user9559069 因为 c# 没有 Number 的等价物。我们能做到的最接近的是将U 限制为既是结构又是IConvertible 的实现者。
【解决方案2】:

取一个数字序列的平均值是 C# 内置的:

var iNums = new int[] { 1, 2, 3, 4, 5 };
var dNums = new double[] { 1.0, 2.0, 3.0, 4.0, 5.0 };

var iAvg = iNums.Average();
var dAvg = dNums.Average();
var areEqual = iAvg == dAvg;

areEqual == true 运行上述后。

您甚至可以使用Average 重载来处理复杂类型,该重载采用Func&lt;TSource, T&gt; 返回值:

public class MyValue
{
    private static Random rnd = new Random();

    public int SomeInt { get; set; } = rnd.Next();
}

var myObjArray = new MyValue[] { new MyValue(), new MyValue(), new MyValue(), new MyValue() };

var myAvg = myObjArray.Average(o => o.SomeInt);

所以不,通配符在 C# 中不可用,但在这种情况下,使用泛型可以通过 Func 的多个重载来模拟通配符。

IEnumerable Methods

【讨论】:

  • 太棒了!喜欢!
  • 好点子!更改实现比试图硬塞到当时语言中不存在的功能要好!
  • PS 有一种方法可以在 C# 中表达通配符...但据我所知,仅适用于 typeof 表达式 typeof(Generic&lt;&gt;) 但我认为这在这里没有用
  • @series0ne 这真的不是像 Java 那样的“通配符”,它是具有单个类型参数的泛型类型引用。它只能评估为单个泛型类型,而不是实现。
  • 我知道,我今天状态不佳。 IIRC 它基本上只是获取泛型类型的 System.Type 实例,而不指定 GTP。所以是的,它不像 Java 中的通配符。
【解决方案3】:

只需使用 double Average 方法添加简单的接口即可:

interface IAbleToGetAverage 
{
    double Average();
}

class GenericClass<T> : IAbleToGetAverage
    where T : struct, IConvertible
{
    private readonly T[] nums; // array of Number or subclass

    public GenericClass(T[] o)
    {
        nums = o;
    }


    private readonly IFormatProvider formatProvider = new NumberFormatInfo();

    public double Average()
    {
        var sum = 0.0;

        for(var i=0; i < nums.Length; i++)
            sum += nums[i].ToDouble(formatProvider);

        return sum / nums.Length;
    }


    public bool SameAvg(IAbleToGetAverage ob)
    {
        if(Math.Abs(Average() - ob.Average()) < double.Epsilon)
            return true;
        return false;
    }
}

【讨论】:

  • 还有...请问.net 框架 int 和 double 数组如何实现该接口?
  • @ZoharPeled...打败我! XD
  • @ZoharPeled 他们不需要。他们只需要他们的Generic_Class 来实现它。
  • Generic_Class 应该实现 IAbleToGetAverage
  • @series0ne 仅供参考,GuidTimeSpan 不是 IConvertible
【解决方案4】:

您在 Java 代码中表达的是装箱的数字类型,它们都扩展了 Number 类,因此强制泛型类型是数字的约束是微不足道的。

类型在 C# 中的工作方式略有不同,因为 int 而不是与 Java 具有相同意义的原始类型,只是 System.Int32 的类型别名,即 struct

此外,在 C# 中没有仅由数字共享的通用基类型(类、结构、接口等),因此它不可能创建一个约束来强制泛型类型是一个数字,有一个数值,因此可以对其进行数学计算。

这里的一些建议似乎是使用where T : IComparable, struct,但我可以争辩说该约束也适用于DateTimeGuidTimeSpan——它们都不是整数或浮点数。

【讨论】:

  • 看似不可能的事情是可能的
  • @gabba 如果您这么认为,请发布一个实际完成的答案。考虑到您发布了一个不起作用的答案,它不支持您声称它是可能的。如果可能的话,您为什么不发布一个实际可行的答案?
  • @seriesOne,Servy IConvertible 不是 IComparable。 IConvertible 有 ToDouble 所以任何 IConvertible 类型都应该正确实现它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多