C#7是否具有数组/可枚举的分解结构?

C#7是否具有数组/可枚举的分解结构?,c#,destructuring,c#-7.0,C#,Destructuring,C# 7.0,在Javascript ES6中,您可以像这样分解数组: const [a,b,...rest] = someArray; var (a,b) = someTuple; public static class EX { public static void Deconstruct<T>(this T[] items, out T t0) { t0 = items.Length > 0 ? items[0] : default(T);

在Javascript ES6中,您可以像这样分解数组:

const [a,b,...rest] = someArray;
var (a,b) = someTuple;
public static class EX
{
    public static void Deconstruct<T>(this T[] items, out T t0)
    {
        t0 = items.Length > 0 ? items[0] : default(T);
    }

    public static void Deconstruct<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);
    }
}
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
其中
a
是数组中的第一个元素,
b
是第二个元素,
rest
是包含其余元素的数组

我知道在C#7中,您可以在赋值过程中对元组进行分解,但找不到任何与分解数组/枚举相关的内容,如:

const [a,b,...rest] = someArray;
var (a,b) = someTuple;
public static class EX
{
    public static void Deconstruct<T>(this T[] items, out T t0)
    {
        t0 = items.Length > 0 ? items[0] : default(T);
    }

    public static void Deconstruct<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);
    }
}
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

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

非常快:没有。

C#还不支持对数组进行分解

目前,我在路线图上也找不到这方面的任何信息。在默认情况下获得这个语法糖之前,似乎需要等待很多时间


正如@Nekeniehl在评论中添加的那样,它可以实现:

如果您想要一个与C语言功能完全集成的解决方案,请使用,这会隐藏一些实现细节。如果你不在乎这一点,你可以用这两个答案中的任何一个


据我所知,没有。然而,制造类似的东西并不难

像这样的扩展方法怎么样:

const [a,b,...rest] = someArray;
var (a,b) = someTuple;
public static class EX
{
    public static void Deconstruct<T>(this T[] items, out T t0)
    {
        t0 = items.Length > 0 ? items[0] : default(T);
    }

    public static void Deconstruct<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);
    }
}
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
缺点是,对于要返回的项目数,您需要一个扩展方法。因此,如果要返回的变量多于几个,则此方法可能不是很有用


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

该语言中没有特殊的语法

不过,您可以利用元组语法来实现这一点

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());
    }
}
类程序
{
静态void Main(字符串[]参数)
{
int[]int=new[]{1,2,3};
var(first,second,rest)=ints.Destruct2();
}
}
公共静态类扩展
{
公共静态(T first,T[]rest)说明1(此T[]项)
{
return(items[0],items.Skip(1.ToArray());
}
公共静态(T first,T second,T[]rest)析构函数2(此T[]项)
{
return(items[0],items[1],items.Skip(2.ToArray());
}
}

(在用于生产代码之前,对于明显的错误场景,应该通过错误处理进行扩展)。

事实证明,不仅元组可以解构,而且任何类型的元组都可以使用具有匹配签名的
解构
静态(或扩展)方法。正确地解构
IEnumerable
并非易事(请参阅David Arno在评论中建议的库),因此让我们看看它如何与simple
IList
一起工作(实现是不相关的,例如,这个实现,当然可以更好/不同):

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

在上一个版本中,您可以使用单个
deconstruct
方法(返回第一个项目和其余项目的方法)解构到任意数量的项目


对于IEnumerables的实际使用,我建议不要重新实现轮子,而是使用另一个答案中提到的David Arno的库。

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

let head :: tail = someCollection
,但没有得到很好的反馈。所以我写了我自己的,你可以通过

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

var (a, (b, rest)) = someArray;
这可能会提供您想要的功能

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.
    }

为了扩展其他贡献者暗示的解决方案,我提供了一个使用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;
}

如果要处理无限流,例如从
while(true)
产生返回
块,则需要稍微小心。在这种情况下,实际上无法检查流的长度以确保有足够的项填充请求的元组

如果您的源代码实际上是无限的,那么上述方法的组合将起作用——而不是计算
IEnumerable
的长度,只需检查它是否有任何内容,然后根据单参数重载实现多参数重载:

    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);
    }
publicstaticvoid解构(这个IEnumerable列表,out T head,out IEnumerable tail)
{
head=list.First();//为空列表抛出InvalidOperationException
尾部=列表。跳过(1);
}
公共静态无效解构(此IEnumerable列表,out T head,out T next,out IEnumerable tail)
{
head=list.First();
(下一步,tail)=list.Skip(1);
}

关键问题是,当流用完时,您希望发生什么。上述代码将抛出一个
InvalidOperationException
。返回默认值可能不是您想要的。在功能上下文中,您通常会执行一个
cons
,将流拆分为一个头部流和一个尾部流,然后在
cons
实现之外检查空流(因此在
解构方法之外)有一种方法可以实现它:因为这个问题是关于C#本身的,我仍然认为我的答案是正确的。很高兴看到可以添加。对不起,我不想说你的答案不对。事实上,这是对的,到目前为止还没有实施,我只是想指出它可以实施=)是的,我也不认为这是批评,不用担心:)谢谢你的信息!将其余的元素作为
IEnumerable
,这很棘手,因为为了获得所需的第一个和第二个元素