C#7有阵​​列/可枚举的解构吗?

时间:2017-12-14 14:24:57

标签: c# destructuring c#-7.0

在Javascript ES6中,您可以像这样构建数组:

const [a,b,...rest] = someArray;

其中a是数组中的第一个元素,b是第二个元素,rest是包含其余元素的数组。

我在C#7中知道你可以在赋值时解构元组,但是找不到任何与解构数组/枚举相关的东西:

var (a,b) = someTuple;

我有一个IEnumerable我需要第一个和第二个元素作为变量,我需要其余的元素作为另一个IEnumerable。我有一个解决方案,但觉得解构会看起来更清洁。

8 个答案:

答案 0 :(得分:17)

事实证明,不仅元组可以被解构,而且任何具有匹配签名的Deconstruct静态(或扩展)方法的类型。正确地为IEnumerable做解构并不是一件容易的事(参见David Arno在评论中提出的库),所以让我们看看它如何与简单的IList一起工作(实现是无关紧要的,这个是为了例子,当然可以更好/不同):

public static class Extensions {
    public static void Deconstruct<T>(this IList<T> list, out T first, out IList<T> rest) {

        first = list.Count > 0 ? list[0] : default(T); // or throw
        rest = list.Skip(1).ToList();
    }

    public static void Deconstruct<T>(this IList<T> list, out T first, out T second, out IList<T> rest) {
        first = list.Count > 0 ? list[0] : default(T); // or throw
        second = list.Count > 1 ? list[1] : default(T); // or throw
        rest = list.Skip(2).ToList();
    }
}

然后(如果需要,在添加相关的using语句之后),您可以使用您想要的语法:

var list = new [] {1,2,3,4};
var (a,rest) = list;
var (b,c,rest2) = list;

或者你可以像这样链解构(因为最后返回的值本身可以被解构):

 var (a, (b, (c, rest))) = list;

使用上一版本,您可以使用单Deconstruct方法解析任意数量的项目(返回第一项和其他项目的方法)。

对于IEnumerables的实际使用,我建议不要重新实现方向盘并使用上面提到的David Arno的库。

答案 1 :(得分:14)

您所描述的内容通常在函数式语言中称为“cons”,通常采用以下形式:

let head :: tail = someCollection

I did propose this be added to C#,但没有得到非常有利的反馈。所以我写了自己的,你可以通过Succinc<T> nuget package使用。

它使用解构来实现任何IEnumerable<T>的头部和尾部的分裂。解构可以嵌套,因此您可以使用它一次性提取多个元素:

var (a, (b, rest)) = someArray;

这可能会提供您所追求的功能。

答案 2 :(得分:8)

不,没有。但是,制作类似的东西并不是很难。

这样的扩展方法怎么样:

public static class EX
{
    public static void Destructure<T>(this T[] items, out T t0)
    {
        t0 = items.Length > 0 ? items[0] : default(T);
    }

    public static void Destructure<T>(this T[] items, out T t0, out T t1)
    {
        t0 = items.Length > 0 ? items[0] : default(T);
        t1 = items.Length > 1 ? items[1] : default(T);
    }
}

你可以像这样使用它:

int[] items = { 1, 2 };

items.Destructure(out int t0);

缺点是您需要每个项目的扩展方法才能返回。因此,如果要返回的变量不止一些,则此方法可能不太有用。

请注意,我没有检查长度和相关内容,但您了解我需要做什么。

答案 3 :(得分:4)

非常快:不。

C#还不支持Arrays的解构。

目前,我在路线图上找不到任何相关信息。似乎在我们默认获得这种语法糖之前会有很多等待。

正如@Nekeniehl在评论中所补充的那样,它可以实现:gist.github.com/waf/280152ab42aa92a85b79d6dbc812e68a

答案 4 :(得分:3)

C#中,您需要自己编写,就像我使用的那样:

public static class ArrayExtensions
    {
        public static void Deconstruct<T>(this T[] array, out T first, out T[] rest)
        {
            first = array.Length > 0 ? array[0] : default(T);
            rest = array.Skip(1).ToArray();
        }

        public static void Deconstruct<T>(this T[] array, out T first, out T second, out T[] rest)
            => (first, (second, rest)) = array;

        public static void Deconstruct<T>(this T[] array, out T first, out T second, out T third, out T[] rest)
            => (first, second, (third, rest)) = array;

        public static void Deconstruct<T>(this T[] array, out T first, out T second, out T third, out T fourth, out T[] rest)
            => (first, second, third, (fourth, rest)) = array;

        public static void Deconstruct<T>(this T[] array, out T first, out T second, out T third, out T fourth, out T fifth, out T[] rest)
            => (first, second, third, fourth, (fifth, rest)) = array;

// .. etc.
    }

然后只需:

var (first, second,_ , rest) = new[] { 1, 2, 3, 4 }

答案 5 :(得分:2)

要扩展其他贡献者暗示的解决方案,我提供一个使用IEnumerable的答案。它可能没有进行优化,但是效果很好。

public static class IEnumerableExt
{
    public static void Deconstruct<T>(this IEnumerable<T> seq, out T first, out IEnumerable<T> rest)
    {
        first = seq.FirstOrDefault();
        rest = seq.Skip(1);
    }

    public static void Deconstruct<T>(this IEnumerable<T> seq, out T first, out T second, out IEnumerable<T> rest)
        => (first, (second, rest)) = seq;

    public static void Deconstruct<T>(this IEnumerable<T> seq, out T first, out T second, out T third, out IEnumerable<T> rest)
        => (first, second, (third, rest)) = seq;

    public static void Deconstruct<T>(this IEnumerable<T> seq, out T first, out T second, out T third, out T fourth, out IEnumerable<T> rest)
        => (first, second, third, (fourth, rest)) = seq;

    public static void Deconstruct<T>(this IEnumerable<T> seq, out T first, out T second, out T third, out T fourth, out T fifth, out IEnumerable<T> rest)
        => (first, second, third, fourth, (fifth, rest)) = seq;
}

然后只需使用以下解构函数:

var list = new[] { 1, 2, 3, 4 };
var (a, b, rest1) = list;
var (c, d, e, f, rest2) = rest1;
Console.WriteLine($"{a} {b} {c} {d} {e} {f} {rest2.Any()}");
// Output: 1 2 3 4 0 0 False

答案 6 :(得分:0)

语言中没有特殊的语法。

但是,您可以利用元组语法来实现此

class Program
{
    static void Main(string[] args)
    {
        int[] ints = new[] { 1, 2, 3 };

        var (first, second, rest) = ints.Destruct2();
    }
}

public static class Extensions
{
    public static (T first, T[] rest) Desctruct1<T>(this T[] items)
    {
        return (items[0], items.Skip(1).ToArray());
    }

    public static (T first, T second, T[] rest) Destruct2<T>(this T[] items)
    {
        return (items[0], items[1], items.Skip(2).ToArray());
    }
}

(在生产代码中使用之前,应该针对明显的错误情况扩展错误处理)。

答案 7 :(得分:0)

如果要处理无限流,则需要格外小心,例如从while(true) yield return块开始。在这种情况下,您实际上无法检查流的长度以确保您有足够的项目来填充请求的元组。

如果您的源实际上是无限的,则可以采用上述方法的组合-而不是计算IEnumerable<T>的长度,只需检查它是否有任何内容,然后实现多参数重载就单参数过载而言:

    public static void Deconstruct<T>(this IEnumerable<T> list, out T head, out IEnumerable<T> tail)
    {
        head = list.First(); // throws InvalidOperationException for empty list

        tail = list.Skip(1);
    }

    public static void Deconstruct<T>(this IEnumerable<T> list, out T head, out T next, out IEnumerable<T> tail)
    {
        head = list.First();
        
        (next, tail) = list.Skip(1);
    }

关键问题是流用尽时要发生什么。上面的代码将抛出InvalidOperationException。返回default<T>可能不是您想要的。在功能性上下文中,通常需要执行cons,然后将流拆分为单个头部和流尾-然后在cons实现之外检查空流(因此在{之外) {1}}方法。