【问题标题】:Using linq to find an object which contains any values使用 linq 查找包含任何值的对象
【发布时间】:2021-03-05 12:22:38
【问题描述】:

我目前在提取所需汽车时遇到问题,该汽车具有轮胎和发动机组合,这是tiresengines 列表的一部分。

我有这个设置:

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


public class Program
{
    public class EngineType
    {
        public string engineName;
    }

    public class Tire
    {
        public string tireName;
    }

    public class Car
    {
        public EngineType enginetype;
        public Tire tire;
    }

    public static void Main()
    {
        List<Car> cars = new List<Car>();
        cars.Add(new Car{enginetype = new EngineType{engineName = "enginea"}, tire = new Tire{tireName = "tirea"}});
        cars.Add(new Car{enginetype = new EngineType{engineName = "engineb"}, tire = new Tire{tireName = "tireb"}});
        cars.Add(new Car{enginetype = new EngineType{engineName = "enginec"}, tire = new Tire{tireName = "tirec"}});

        List<Tire> tires = new List<Tire>();
        tires.Add(new Tire{tireName = "tirea"});
        tires.Add(new Tire{tireName = "tired"});
        tires.Add(new Tire{tireName = "tiree"});
         
        List<EngineType> engineTypes = new List<EngineType>();
        engineTypes.Add(new EngineType{engineName = "enginea"});
        engineTypes.Add(new EngineType{engineName = "engined"});
        engineTypes.Add(new EngineType{engineName = "enginec"});
                           
        var selectedcars = cars.Where(x=> engineTypes.Contains(x.enginetype) && tires.Contains(x.tire));

        Console.WriteLine(selectedcars.Count);                 
    }
}

这虽然给了我一个错误,因为selectedcars 不包含 为什么不呢?

小提琴:https://dotnetfiddle.net/WcVcnr

【问题讨论】:

  • 您需要selectedcars.Count()。此外,您的代码将失败,因为 engineTypescarTypes 中的引用指向的对象与 Car 对象中的引用不同。
  • so contains 不要按值比较,而是按引用比较?有没有可能按价值来做?
  • @J.Down:它们是类,所以引用类型,你没有覆盖Equals,所以使用Object.Equals,它使用ReferenceEquals并忽略你的名字。 EngineTypeTire 可以实现 IEquatable&lt;T&gt; 或者您可以实现自定义 IEqualityComparer&lt;T&gt; 来比较名称,并且可以在 LINQ 方法中使用,例如 Enumerable.Contains
  • 或根据身份解析填充对象图。即:首先创建 engineTypes 并将 Car.EngineType 设置为现有类型之一。轮胎也一样。这类似于大多数 ORM 填充对象图的方式。

标签: c# .net linq


【解决方案1】:

您比较具有相同值的不同对象,因此它无法工作。 你有多种选择来解决这个问题:

第一个选项:使用值

   IEnumerable<Car> selectedcars = cars.Where(x => engineTypes.Select(e => e.engineName).Contains(x.enginetype.engineName) && tires.Select(t => t.tireName).Contains(x.tire.tireName));

此行将提取值以比较它们,因此无论它是否是同一个对象。 显然,如果您根本不关心这些对象,则根本无法使用它们:

        var tires = new []
        {
            "tirea",
            "tired",
            "tiree"
        };

        var engineTypes = new []
        {
            "enginea",
            "engined",
            "enginec"
        };

        IEnumerable<Car> selectedcars = cars.Where(x => engineTypes.Contains(x.enginetype.engineName) && tires.Contains(x.tire.tireName));

顺便说一句,如果只是一个字符串就足够了,而且你不关心真正有一个“轮胎”或“引擎类型”类,你可以枚举。

第二个选项:记录

如果您使用 C# 9 或更高版本并且您只关心 Tire 和 EngineType 内部的值(所以只关心内部字段的值,无论它们是否是不同的对象)您可以使用“记录”功能。使用该机制比较两个对象时,只会考虑值,而不是参考:

    public record EngineType
    {
        public string engineName;
    }

    public record Tire
    {
        public string tireName;
    }

第三种选择:实现 Equals(@Tim Schmelter 在评论中提出)

如果您喜欢以前的解决方案但不能使用“记录”功能,您可以确保您的类实现“IEquatable”接口。这样您就可以表明您希望如何比较(而不是参考):

        public class EngineType
    {
        public string engineName;

        public bool Equals(EngineType other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return engineName == other.engineName;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((EngineType) obj);
        }

        public override int GetHashCode()
        {
            return (engineName != null ? engineName.GetHashCode() : 0);
        }
    }

    public class Tire
    {
        public string tireName;

        protected bool Equals(Tire other)
        {
            return tireName == other.tireName;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((Tire) obj);
        }

        public override int GetHashCode()
        {
            return (tireName != null ? tireName.GetHashCode() : 0);
        }
    }

(此代码已生成,所以我认为它是正确的)

您的查询仍然可以:

var selectedcars = cars.Where(x => engineTypes.Contains(x.enginetype) && tires.Contains(x.tire));

第四个选项:实现一个比较器(由@Tim Schmelter 在评论中提出)

如果您仍然喜欢以前的选项,但您不能修改对象本身(可能它已经生成或者是 nuget 包的一部分,...),您可以在它们之外实现一个比较器:

    public class EngineTypeComparer : IEqualityComparer<EngineType>
    {
        public bool Equals(EngineType x, EngineType y)
        {
            return x.engineName == y.engineName;
        }

        public int GetHashCode(EngineType obj)
        {
            return obj.x.engineName.GetHashCode(); }

    }

    public class TireComparer : IEqualityComparer<Tire>
    {
        public bool Equals(Tire x, Tire y)
        {
            return x.tireName == y.tireName;
        }

        public int GetHashCode(Tire obj)
        {
            return obj.tireName.GetHashCode();
        }
    }

(在那些比较器中没有实现空值检查,但显然应该实现);

在查询中,您必须指定要使用的比较器:

   var selectedcars = cars.Where(x => engineTypes.Contains(x.enginetype, new EngineTypeComparer()) && tires.Contains(x.tire, new TireComparer()));

【讨论】:

  • EngineTypeTire 可以实现 IEquatable&lt;T&gt; 或者您可以实现自定义 IEqualityComparer&lt;T&gt; 来比较名称。
  • 确实,但如果可能的话,我更喜欢“记录”方法。我将更新答案以包含您的解决方案。
  • 还不知道 C#9 的记录功能,感谢您提供的信息。你不需要构造函数吗?
猜你喜欢
  • 1970-01-01
  • 2017-03-09
  • 2019-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多