C# IEnumerable、Where和Object.ReferenceEquals存在问题

C# IEnumerable、Where和Object.ReferenceEquals存在问题,c#,.net-core,ienumerable,.net-core-3.0,C#,.net Core,Ienumerable,.net Core 3.0,试图解决一个问题(多米诺骨牌)。在这个问题中,我试图创建一个有效的多米诺骨牌链,使数字相互接触,并且第一个和最后一个数字相同 示例:[1 | 2]、[3,1]、[3,2]->[1 | 2][2 | 3][3 | 1] 我使用蛮力来尝试所有的组合。为此,我使用以下代码启动一个包含所有可能组合的链: using System; using System.Collections.Generic; using System.Linq; public static class Dominoes {

试图解决一个问题(多米诺骨牌)。在这个问题中,我试图创建一个有效的多米诺骨牌链,使数字相互接触,并且第一个和最后一个数字相同

示例:
[1 | 2]、[3,1]、[3,2]->[1 | 2][2 | 3][3 | 1]

我使用蛮力来尝试所有的组合。为此,我使用以下代码启动一个包含所有可能组合的链:

using System;
using System.Collections.Generic;
using System.Linq;

public static class Dominoes
{
    private class Domino
    {
        public readonly int First;
        public readonly int Last;

        public Domino(int first, int last)
        {
            First = first;
            Last = last;
        }

        public Domino Flip() => new Domino(Last, First);
    }

    private class Chain
    {
        private readonly List<Domino> chained = new List<Domino>();

        private readonly List<Domino> remaining = new List<Domino>();

        private int First => chained[0].First;

        private int Last => chained[chained.Count - 1].Last;

        public bool Complete => remaining.Count == 0 && First == Last;

        private Chain(Domino domino, IEnumerable<Domino> remaining, IEnumerable<Domino> chained = null)
        {
            if (chained != null)
            {
                this.chained.AddRange(chained);
            }

            this.chained.Add(domino);

            this.remaining.AddRange(remaining);
        }

        public static IEnumerable<Chain> Start(IEnumerable<Domino> dominoes)
        {
            var chains = new List<Chain>();

            foreach (var domino in dominoes)
            {
                var remaining = dominoes.Where(d => d != domino);

                chains.Add(new Chain(domino, remaining));
                chains.Add(new Chain(domino.Flip(), remaining));
            }

            return chains;
        }

        public IEnumerable<Chain> Extend()
        {
            var chains = new List<Chain>();

            foreach (var domino in this.remaining)
            {
                var remaining = this.remaining.Where(d => d != domino);

                if (domino.First == Last)
                {
                    chains.Add(new Chain(domino, remaining, chained));
                }

                if (domino.Last == Last)
                {
                    chains.Add(new Chain(domino.Flip(), remaining, chained));
                }
            }

            return chains;
        }
    }

    public static bool CanChain(IEnumerable<(int, int)> dominoes)
    {
        var chains = Chain.Start(dominoes.Select(d => new Domino(d.Item1, d.Item2)));

        return chains.Any() ? Iterate(chains) : true;
    }

    private static bool Iterate(IEnumerable<Chain> chains)
    {
        var newChains = new List<Chain>();

        foreach (var chain in chains)
        {
            if (chain.Complete) return true;

            newChains.AddRange(chain.Extend());
        }

        if (newChains.Count == 0) return false;

        return Iterate(newChains);
    }
}
private class Foobar {
    private readonly int value;

    public Foobar(int v) {
        Console.WriteLine("## CONSTRUCTOR ## Foobar object created with value: "+v);
        this.value = v;
    }

    public override string ToString() {
        return "Foobar("+this.value+")";
    }
}

public static void Main(string[] args) {
    int[] numbers = new [] { 1, 5};
    IEnumerable<Foobar> range = numbers.Select(i => new Foobar(i));

    Console.WriteLine(range.Count());
    foreach (Foobar entry in range) {
        Console.WriteLine("Build an enumerable without "+entry);
        IEnumerable<Foobar> remaining = range.Where(it => it != entry);
        Console.WriteLine("The length of the remaining: "+remaining.Count());
        foreach (Foobar remainingEntry in remaining) {
            Console.WriteLine("Entry of remaining: "+remainingEntry);
        }
    }
}
使用
dotnet测试运行

链中剩余
的筛选。开始
似乎不起作用

示例(使用
new[]{(1,2)}
作为
CanChain
的输入)

如果我在
多米诺骨牌上调用
.ToList()
。在
CanChain
中选择(d=>newdomino(d))
,它将开始工作


编辑:添加了更多信息

问题在于,
Select
的枚举没有立即执行,而是延迟执行。这意味着每次访问/使用
Select()
方法返回的
IEnumerable
时,您都会得到一个新的列表。但这也意味着您一直在创建新的
Domino
对象。由于您没有覆盖
Equals()
方法,因此这些
Domino
对象中的每一个都将不同,即使它们具有相同的值

要显示问题,请检查以下代码:

using System;
using System.Collections.Generic;
using System.Linq;

public static class Dominoes
{
    private class Domino
    {
        public readonly int First;
        public readonly int Last;

        public Domino(int first, int last)
        {
            First = first;
            Last = last;
        }

        public Domino Flip() => new Domino(Last, First);
    }

    private class Chain
    {
        private readonly List<Domino> chained = new List<Domino>();

        private readonly List<Domino> remaining = new List<Domino>();

        private int First => chained[0].First;

        private int Last => chained[chained.Count - 1].Last;

        public bool Complete => remaining.Count == 0 && First == Last;

        private Chain(Domino domino, IEnumerable<Domino> remaining, IEnumerable<Domino> chained = null)
        {
            if (chained != null)
            {
                this.chained.AddRange(chained);
            }

            this.chained.Add(domino);

            this.remaining.AddRange(remaining);
        }

        public static IEnumerable<Chain> Start(IEnumerable<Domino> dominoes)
        {
            var chains = new List<Chain>();

            foreach (var domino in dominoes)
            {
                var remaining = dominoes.Where(d => d != domino);

                chains.Add(new Chain(domino, remaining));
                chains.Add(new Chain(domino.Flip(), remaining));
            }

            return chains;
        }

        public IEnumerable<Chain> Extend()
        {
            var chains = new List<Chain>();

            foreach (var domino in this.remaining)
            {
                var remaining = this.remaining.Where(d => d != domino);

                if (domino.First == Last)
                {
                    chains.Add(new Chain(domino, remaining, chained));
                }

                if (domino.Last == Last)
                {
                    chains.Add(new Chain(domino.Flip(), remaining, chained));
                }
            }

            return chains;
        }
    }

    public static bool CanChain(IEnumerable<(int, int)> dominoes)
    {
        var chains = Chain.Start(dominoes.Select(d => new Domino(d.Item1, d.Item2)));

        return chains.Any() ? Iterate(chains) : true;
    }

    private static bool Iterate(IEnumerable<Chain> chains)
    {
        var newChains = new List<Chain>();

        foreach (var chain in chains)
        {
            if (chain.Complete) return true;

            newChains.AddRange(chain.Extend());
        }

        if (newChains.Count == 0) return false;

        return Iterate(newChains);
    }
}
private class Foobar {
    private readonly int value;

    public Foobar(int v) {
        Console.WriteLine("## CONSTRUCTOR ## Foobar object created with value: "+v);
        this.value = v;
    }

    public override string ToString() {
        return "Foobar("+this.value+")";
    }
}

public static void Main(string[] args) {
    int[] numbers = new [] { 1, 5};
    IEnumerable<Foobar> range = numbers.Select(i => new Foobar(i));

    Console.WriteLine(range.Count());
    foreach (Foobar entry in range) {
        Console.WriteLine("Build an enumerable without "+entry);
        IEnumerable<Foobar> remaining = range.Where(it => it != entry);
        Console.WriteLine("The length of the remaining: "+remaining.Count());
        foreach (Foobar remainingEntry in remaining) {
            Console.WriteLine("Entry of remaining: "+remainingEntry);
        }
    }
}
正如您所见,构造函数被调用的次数太多了。每次有人使用
Select()
调用中的
IEnumerable
时,它将再次生成可枚举列表,这样就不会干扰相同数据上的其他并行运行迭代。但是,由于在
Select()
语句中创建了新对象,因此每次创建
Select()
IEnumerable
,都会得到新对象

正如您已经注意到的,要解决这个问题,您需要使用类似于
ToList()
的方法,并一次性评估/创建
Domino
对象。使用一个
ToList()
调用输出更改,如下所示:

###构造函数##Foobar对象创建值:1
##构造函数##Foobar对象创建值:5
2.
构建不带Foobar的可枚举文件(1)
剩余的长度:1
输入剩余:Foobar(5)
构建不带Foobar的可枚举文件(5)
剩余的长度:1
输入剩余:Foobar(1)

你看,只创建了两个对象,并且
=将按预期工作,因为只有这两个对象。

问题是,
Select
的枚举没有立即执行,而是被延迟。这意味着每次访问/使用
Select()
方法返回的
IEnumerable
时,您都会得到一个新的列表。但这也意味着您一直在创建新的
Domino
对象。由于您没有覆盖
Equals()
方法,因此这些
Domino
对象中的每一个都将不同,即使它们具有相同的值

要显示问题,请检查以下代码:

using System;
using System.Collections.Generic;
using System.Linq;

public static class Dominoes
{
    private class Domino
    {
        public readonly int First;
        public readonly int Last;

        public Domino(int first, int last)
        {
            First = first;
            Last = last;
        }

        public Domino Flip() => new Domino(Last, First);
    }

    private class Chain
    {
        private readonly List<Domino> chained = new List<Domino>();

        private readonly List<Domino> remaining = new List<Domino>();

        private int First => chained[0].First;

        private int Last => chained[chained.Count - 1].Last;

        public bool Complete => remaining.Count == 0 && First == Last;

        private Chain(Domino domino, IEnumerable<Domino> remaining, IEnumerable<Domino> chained = null)
        {
            if (chained != null)
            {
                this.chained.AddRange(chained);
            }

            this.chained.Add(domino);

            this.remaining.AddRange(remaining);
        }

        public static IEnumerable<Chain> Start(IEnumerable<Domino> dominoes)
        {
            var chains = new List<Chain>();

            foreach (var domino in dominoes)
            {
                var remaining = dominoes.Where(d => d != domino);

                chains.Add(new Chain(domino, remaining));
                chains.Add(new Chain(domino.Flip(), remaining));
            }

            return chains;
        }

        public IEnumerable<Chain> Extend()
        {
            var chains = new List<Chain>();

            foreach (var domino in this.remaining)
            {
                var remaining = this.remaining.Where(d => d != domino);

                if (domino.First == Last)
                {
                    chains.Add(new Chain(domino, remaining, chained));
                }

                if (domino.Last == Last)
                {
                    chains.Add(new Chain(domino.Flip(), remaining, chained));
                }
            }

            return chains;
        }
    }

    public static bool CanChain(IEnumerable<(int, int)> dominoes)
    {
        var chains = Chain.Start(dominoes.Select(d => new Domino(d.Item1, d.Item2)));

        return chains.Any() ? Iterate(chains) : true;
    }

    private static bool Iterate(IEnumerable<Chain> chains)
    {
        var newChains = new List<Chain>();

        foreach (var chain in chains)
        {
            if (chain.Complete) return true;

            newChains.AddRange(chain.Extend());
        }

        if (newChains.Count == 0) return false;

        return Iterate(newChains);
    }
}
private class Foobar {
    private readonly int value;

    public Foobar(int v) {
        Console.WriteLine("## CONSTRUCTOR ## Foobar object created with value: "+v);
        this.value = v;
    }

    public override string ToString() {
        return "Foobar("+this.value+")";
    }
}

public static void Main(string[] args) {
    int[] numbers = new [] { 1, 5};
    IEnumerable<Foobar> range = numbers.Select(i => new Foobar(i));

    Console.WriteLine(range.Count());
    foreach (Foobar entry in range) {
        Console.WriteLine("Build an enumerable without "+entry);
        IEnumerable<Foobar> remaining = range.Where(it => it != entry);
        Console.WriteLine("The length of the remaining: "+remaining.Count());
        foreach (Foobar remainingEntry in remaining) {
            Console.WriteLine("Entry of remaining: "+remainingEntry);
        }
    }
}
正如您所见,构造函数被调用的次数太多了。每次有人使用
Select()
调用中的
IEnumerable
时,它将再次生成可枚举列表,这样就不会干扰相同数据上的其他并行运行迭代。但是,由于在
Select()
语句中创建了新对象,因此每次创建
Select()
IEnumerable
,都会得到新对象

正如您已经注意到的,要解决这个问题,您需要使用类似于
ToList()
的方法,并一次性评估/创建
Domino
对象。使用一个
ToList()
调用输出更改,如下所示:

###构造函数##Foobar对象创建值:1
##构造函数##Foobar对象创建值:5
2.
构建不带Foobar的可枚举文件(1)
剩余的长度:1
输入剩余:Foobar(5)
构建不带Foobar的可枚举文件(5)
剩余的长度:1
输入剩余:Foobar(1)

你看,只创建了两个对象,并且
=将按预期工作,因为只有这两个对象。

Domino是定义为类还是结构?
对象。只有当类(在本例中是Domino)重载了它的
=
时,才需要ReferenceEquals
,这似乎不太可能。如果您能够显示
Domino
代码,这将非常有帮助。同意不需要
Object.Reference
。添加了
Domino
code你能再发布一点代码吗?我们可以复制并运行它来产生意想不到的结果。调试会更容易。当我运行它并传入一个domino时,剩余的
会像预期的那样变为空。domino是定义为类还是结构?
对象。只有当类(本例中的domino)重载了它的
=
时,才需要ReferenceEquals,这似乎不太可能。如果您能够显示
Domino
代码,这将非常有帮助。同意不需要
Object.Reference
。添加了
Domino
code你能再发布一点代码吗?我们可以复制并运行它来产生意想不到的结果。调试这个会更容易。当我运行它并传入一个domino时,
剩余的
会像预期的那样变成空的。我想它应该是沿着这些线的东西。我想它应该是沿着这些线的东西。