【问题标题】:C# LINQ Generic Extension Method to check if sequence is the same and return it's valueC# LINQ 通用扩展方法检查序列是否相同并返回它的值
【发布时间】:2016-06-21 17:01:11
【问题描述】:

我正在尝试组合一个扩展方法,该方法允许我检查序列的所有值是否相同,如果是则返回该值,或者如果它是 int 或 double 则不返回该值的平均值如果是字符串,则返回该值的串联。

例如下面的对象和一个列表

public class Item
{
    public int Size {get; set;}
    public string Name {get; set;}
}

var itemsTheSame = new List<Item>
{
    new Item { Size = 1, Name = "Red" },
    new Item { Size = 1, Name = "Red" },
    new Item { Size = 1, Name = "Red" }
};

我希望能够做这样的事情

itemsTheSame.AllEqual(item => item.Size)); // should return 1 as an int
itemsTheSame.AllEqual(item => item.Name)); // should return "Red" as a string

我的泛型不是很强大,但这是我开始的

public static class Extensions
{
    public static int AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, int> selector)
    {
        if (!list.Any()) return default(int);
        var first = list.First();
        return list.Skip(1).All(selector == first) ? default(int) : default(int);
    }
}

对我来说很明显返回类型也需要是通用的,所以想出了这个(不编译)

public static TReturn AllEqual<TSource, TReturn>(this IEnumerable<TSource> list, Func<TSource, TReturn> selector)
{
    if (!list.Any()) return default(TReturn);
    var first = list.First(selector);
    return list.Skip(1).All(selector == first) ? default(TReturn) : default(TReturn);
}

我完全糊涂了,因为我需要能够将Func&lt;TSource, TReturn&gt; selector 传递给.First() 才能获得值本身,但.First 需要Func&lt;TSource, bool&gt;。我还需要能够将相同的选择器传递给.All,因为我只想检查选择器中指定的字段是否相等。

这是否可能,或者我最好拥有一组重载方法,一个返回一个 int,另一个返回一个字符串等?

小提琴here

【问题讨论】:

  • 这听起来有点混乱。例如“红色”的平均值是多少?
  • 你的方法签名可以是public static TSource AllEqual&lt;TSource&gt;...
  • @stuartd 在这种情况下看到 TSource 将是 typeof(string) 我希望它返回不同值的串联。

标签: c# linq generics


【解决方案1】:

First 不接受选择器,它接受谓词。要将选择器应用于第一项,您需要获取第一项(使用其无参数重载),然后在该结果上调用选择器。

并且要比较未知类型的对象是否相等,您将无法使用==operator。它是在编译时绑定的,因此它无法解释该类型的特定实现,除了它可能没有这一事实。您应该使用IEqualityComparer&lt;T&gt; 来比较项目是否相等(如果没有提供,您可以使用默认比较器)。

另请注意,您的实现最多可迭代源可枚举 3 次。如果它实际上是一个查询,而不是一个已经物化的集合,这很容易成为一个问题。你最好只显式地迭代序列,而不是在这里使用 LINQ 操作,因为你不能只用一个 LINQ 查询来做到这一点。

【讨论】:

    【解决方案2】:
    1. 尽量避免重复IEnumerable 多次。它可能太贵了,你永远不知道。
    2. 由于平均功能,您需要为每种数字类型实现一个实现。
    3. 无论您是否具有所有相同的值,平均值实际上都是相同的。只需使用 Average 扩展方法。您可能想要包装它以使语法相同。 (或者考虑给它一个不同的名字,比如AllEqualOrAverage,因为它不一样。)
    4. int 的平均值通常是double。决定是要舍入它、使用 floor 还是实际返回 double
    5. 您需要Select(selector) 才能从项目中获取值。 First 的参数是一个谓词,如Where(predicate)

    建议:

    // for doubles (returning average or default if list is empty)
    public static double AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, double> selector)
    {
        // use IList<T> interface to quickly check the size of the
        // list if this interface is available. Use ToArray otherwise.
        var ilist = list as IList<TSource> ?? list.ToArray();
        if (ilist.Count == 0) return 0.0;
        list.Select(selector).Average();
    }
    
    // for ints (returning average or default if list is empty)
    public static double AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, int> selector)
    {
        // use IList<T> interface to quickly check the size of the
        // list if this interface is available. Use ToArray otherwise.
        var ilist = list as IList<TSource> ?? list.ToArray();
        if (ilist.Count == 0) return 0.0;
        list.Select(selector).Average();
    }
    
    // for everything else (returning unique value or default)
    public static TReturn AllEqual<TSource, TReturn>(this IEnumerable<TSource> list, Func<TSource, TReturn> selector)
    {
        // put 2 distinct values into an array
        var distinctItems.Select(selector).Distinct().Take(2).ToArray();
        if (distinct.Length == 1) return distinct[0];
        default(TReturn);
    }
    

    请注意,这只适用于您在编译时知道数字类型的情况。如果你需要返回一个只能在运行时识别的数字的平均值(它在编译时是一个对象),那就很难了。

    【讨论】:

    • 在计算平均值之前执行 Distinct 会完全弄乱该平均值。
    • 我知道并且也说过这个。反正现在已经修好了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多