使用 linq 查找包含任何值的对象

时间:2021-03-05 12:22:38

标签: c# .net linq

我目前在提取所需汽车时遇到问题,该汽车具有轮胎和发动机组合,属于列表 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

1 个答案:

答案 0 :(得分:3)

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

第一个选项:使用值

   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));

顺便说一下,如果你只需要一个字符串就足够了,而你又不想真正拥有“Tire”或“EngineType”类,那么你可以枚举。

第二个选项:记录

如果您使用的是 C# 9 或更高版本并且您只关心 Tyre 和 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()));