Reputation: 775
I am currently having an issue with extracting the desired car, which has a tire and engine combinations, which is part of the list tires
and list of engines
.
I have this setup:
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);
}
}
this though gives me an error, since selectedcars
dont contain the cars which
why not?
fiddle: https://dotnetfiddle.net/WcVcnr
Upvotes: 0
Views: 59
Reputation: 1909
You compare differents objects having the same value so it cannot works. You have multiple options to solve that :
First option : Use the values
IEnumerable<Car> selectedcars = cars.Where(x => engineTypes.Select(e => e.engineName).Contains(x.enginetype.engineName) && tires.Select(t => t.tireName).Contains(x.tire.tireName));
This line will extract the value to compare them so no matter if it's the same object or not. Obviously if you don't care at all about the objects you could simply not use them :
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));
By the way, if just a string is enough for you and you don't care to really have a "Tire" or "EngineType" class you can just enum.
Second option : Records
If you're in C# 9 or above and you only care about the value inside Tire and EngineType (so only the value of the inner fields, no matter if those are differents objects) you can use the "record" feature. With that mechanism when comparing two objects only the values will be taken into account, not the reference :
public record EngineType
{
public string engineName;
}
public record Tire
{
public string tireName;
}
Third option: Implement the Equals (Proposed by @Tim Schmelter in the comment)
If you love the previous solution but you cannot use the "record" feature, you can ensure your class implement the "IEquatable" interface. That way you can indicate how you want think to be compared (instead of the reference):
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);
}
}
(This code has been generated so I assumed it's correct)
And your query can still the same :
var selectedcars = cars.Where(x => engineTypes.Contains(x.enginetype) && tires.Contains(x.tire));
Fourth option: Implement a comparer (Proposed by @Tim Schmelter in the comment)
If you still like the previous options but you cannot modify the objects itself (maybe it has been generated or is part of a nuget package, ...) you can implement outside of them a comparer :
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();
}
}
(The null check are not implement in those comparer but obviously it should be);
And in the query you have to specify wich comparer you want to use :
var selectedcars = cars.Where(x => engineTypes.Contains(x.enginetype, new EngineTypeComparer()) && tires.Contains(x.tire, new TireComparer()));
Upvotes: 3