This question already has answers here:
List<string> complex sorting
(5 answers)
Closed 6 years ago.
I have a list of items and I know I can order them alphabetically by the property name, but how can I order them by the actual value and not the property.
For clarification let's say I have this code:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).ToList();
Now I know I can order alphabetically by the FavoriteName or vice versa, but for arguments sake let's say the Names that are in the database are "Nick", "Adam", "Chris" and if I used the OrderBy method:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).OrderBy(x => x.FavoriteName).ToList();
this would list the names alphabetically "Adam", "Chris","Nick", and vice versa with:
List<string> lstNames = db.TestDB.Where(x => x.TestCode == true).Select(x => x.FavoriteName).OrderBy(x => x.FavoriteName).OrderByDescending(x => x.FavoriteName).ToList();
But what if I wanted to order them as "Chris", "Adam", "Nick"?
Basically I am looking for a custom way to order a list by the actual values and not the property name?
Also, these values that are in my database will not be changed and I doubt any will be added/deleted.. so I have also tried putting all of the values into their own list and tried the OrderBy method with that list but it did not work.
Any help is appreciated. Thanks.
You can create a custom comparer for sorting:
public class MyComparer : IComparer<string>
{
public int Compare(string stringA, string stringB)
{
// your comparison logic
// return 1 if stringA "is greater than" stringB
// return -1 if stringA "is less than" stringB
// return 0 if they are equal
}
}
And use that when ordering:
lstNames.OrderBy(x => x, instanceOfMyComparer)
The comparison logic which defines your ordering in the example given in the question doesn't really make sense to me. But that's not particularly important. Whatever logic you want to use would simply go in that Compare() method.
If the ordering is arbitrary but you know what it should be, I'd suggest adding another table to your DB that has the FavoriteName and the order #, join to that and then order the result.
If you don't want to do that in the DB, you can accomplish the same thing by hardcoding it, something like this:
var nameOrder = new[] {
new {Name = "Chris", Order = 1}
, new {Name = "Adam", Order = 2}
, new {Name = "Nick", Order = 3}
};
lstNames.Join(nameOrder, lstName => lstName, nameOrd => nameOrd.Name, (Name, nameOrd) => new { Name, nameOrd.Order }).OrderBy(o => o.Order);
Related
I have a below linq query and getting data like below example want to remove duplications
List<EmployeeSalary> lstEmployeeSalary =
new EmployeeSalaryFactory().GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(m => (EmployeeSalary)m)
.ToList();
For ex.:
Id Name EmpCode Salary DateOfSalary
-------------------------------------------------------------
1 Item1 IT00001 $100 5/26/2021
2 Item2 IT00002 $200 4/26/2021
3 Item3 IT00003 $150 5/26/2021
1 Item1 IT00001 $100 4/26/2021
3 Item3 IT00003 $150 4/26/2021
Output
Id Name EmpCode Salary DateOfSalary
-------------------------------------------------------------
1 Item1 IT00001 $100 5/26/2021
2 Item2 IT00002 $200 4/26/2021
3 Item3 IT00003 $150 5/26/2021
If suppose that new EmployeeSalaryFactory().GetRelatedObjects(...) returns list of EmployeeSalary objects:
List<EmployeeSalary> lstEmployeeSalary =
new EmployeeSalaryFactory().GetRelatedObjects(...)
.GroupBy(x => x.Id)
.Select(g => g.OrderByDescending(o => o.DateOfSalary).First());
Test
IList<EmployeeSalary> clients = new List<EmployeeSalary>()
{
new EmployeeSalary { Id=1, Name = "Item1", EmpCode="IT00001", Salary=100, DateOfSalary= new DateTime(2021,5,26)},
new EmployeeSalary { Id=2, Name = "Item2", EmpCode="IT00002", Salary=200, DateOfSalary= new DateTime(2021,4,26)},
new EmployeeSalary { Id=3, Name = "Item3", EmpCode="IT00003", Salary=150, DateOfSalary= new DateTime(2021,5,26)},
new EmployeeSalary { Id=1, Name = "Item1", EmpCode="IT00001", Salary=100, DateOfSalary= new DateTime(2021,4,26)},
new EmployeeSalary { Id=3, Name = "Item3", EmpCode="IT00003", Salary=150, DateOfSalary= new DateTime(2021,4,26)},
}
var res = clients.GroupBy(x => new { x.Id, x.Name, x.EmpCode, x.Salary })
.Select(g => g.OrderByDescending(o => o.DateOfSalary).First());
foreach (var it in res.ToList())
{
System.Diagnostics.Debug.WriteLine(it.Id + ", " + it.Name + ", " + it.EmpCode + ", " + it.Salary + ", " + it.DateOfSalary);
}
OUTPUT:
1, Item1, IT00001, 100, 5/26/2021 00:00:00
2, Item2, IT00002, 200, 4/26/2021 00:00:00
3, Item3, IT00003, 150, 5/26/2021 00:00:00
First of all, don't do the ToList() inside your procedures, unless you will be using the fact that the result is a List<EmployeeSalary>.
If you only intend to return the fetched data to your caller, consider to return IEnumerable<EmployeeSalary> and let the caller do the ToList.
The reason for this, is that if you caller doesn't want to use all fetched data, it would be a waste of processing power to materialize it all:
Suppose you have the following methods to get the EmployeeSalaries:
private EmployeeSalaryFactory {get;} = new EmployeeSalaryFactory();
IEnumerable<EmployeeSalary> GetEmployeeSalaries()
{
return this.EmployeeSalaryFactory
.GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(m => (EmployeeSalary)m);
}
It might be that inValue, ddlPayDate, etc are parameters of this method, but that's outside the question.
Now let's use this method:
EmployeeSalary GetSalary(int employeeId)
{
return this.GetEmployeeSalaries()
.Where(salary => salary.EmployeeId == employeeId)
.FirstOrDefault();
}
If GetEmployeeSalaries would have returned a List<EmployeeSalary> then all salaries would have been materialized, while the caller might only needed a few.
Back to your question
I want to remove duplications
The answer depends on what you would call a duplicate: When are two EmployeeSalaries equal? Is that if all properties have equal value, or are two salaries equal if they have the same Id (but possibly different Salary).
I assume the first: all values should be checked for equality
The quick solution
If you only need to do this for this usage only, if you don't need to massively unit test it, don't need to prepare for future changes, don't want to be able to reuse the code for similar problems, consider to use Queryable.Distinct before your Select.
The result of
Of course, if the data is in your local process (not in a database), you can use the IEnumerable equivalent.
var uniqueSalaries = this.EmployeeSalaryFactory
.GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(salary => new
{
// Select all properties that you need to make a Salary:
Id = salary.Id,
Name = salary.EmpCode,
Salary = salary.Salary,
Date = salary.DateOfSalary,
})
.Distinct()
Before the Distinct, the selected objects are of anonymous type. They have a default equality comparer that compares by value, not by reference. So two objects of this anonymous type that have equal value for every property are considered to be equal. Distinct will remove duplicates.
If you really need that the result is IEnumerable<EmployeeSalary>, you'll need a second select:
.Select(uniqueSalary => new EmployeeSalary
{
Id = uniqueSalary.Id,
Name = uniqueSalary.Name,
...
});
Proper solution
If the input data is in your local process (= it is IEnumerable), you have more LINQ methods at your disposal, like the overload of Enumerable.Distinct that has a parameter EqualityComparer.
In that case, my advice would be to create an Equality comparer for EmployeeSalaries. This will have the advantage that you can reuse the equality comparer for other EmployeeSalary problems. The code will look easier to read. You are prepared for future changes: if you add or remove a property from your definition of equality, for instance if you only need to check the Id, there is only one place that you have to change. You can unit test the comparer: didn't you forget some properties?
private EmployeeSalaryFactory {get;} = new EmployeeSalaryFactory();
private IEqualityComparer<EmployeeSalary> SalaryComparer {get} = ...;
private IEnumerable<EmployeeSalary> GetEmployeeSalaries() { ... see above }
To get the unique salaries:
IEnumerable<EmployeeSalary> uniqueSalaries = this.GetEmployeeSalaries()
.Distinct(this.SalaryComparer);
Did you notice, that because I reuse a lot of code, the specific problem of unique salaries is quite easy to understand.
I cheated a little, I moved the problem to the equality comparer.
IEquality
Creating a reusable equality comparer is fairly straightforward. The advantage is that you can reuse it in all cases where you need to compare EmployeeSalaries. If in future your definition of equality changes, there is only one place that you need to change. Finally: only one place where you need to unit test whether you implemented the proper definition of equality.
public class EmployeeSalaryComparer : EqualityComparer<EmployeeSalary>()
{
public static IEqualityComparer<EmployeeSalary> ByValue {get} = new EmployeeSalaryComparer;
public override bool Equals (EmployeeSalary x, EmployeeSalary y) {...}
public override int GetHashCode (EmployeeSalary x) {...}
}
Usage would be:
IEqualityComparer<EmployeeSalary> salaryComparer = EmployeeSalaryComparer.ByValue;
EmployeeSalary employee1 = ...
EmployeeSalary employee2 = ...
bool equal = salaryComparer.Equals(employee1, employee2);
Implement equality
public override bool Equals (EmployeeSalary x, EmployeeSalary y)
{
Almost all equality comparers start with the following lines:
if (x == null) return y == null; // true if both null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true; // same object
if (x.GetType() != y.GetType() return false;
After this, the real comparing for equality starts. The implementation depends on what you call equality. You might say: same Id is equal EmployeeSalary. Our aproach is to check all fields, for instance to see if we need to update the database, because some values are changed:
return x.Id == y.Id
&& x.Name == y.Name
&& x.EmpCode == y.EmpCode
&& x.Salary == y.Salary
&& x.DateOfSalary == y.DateOfSalary;
}
Are in your definition the names: "John Doe" and "john doe" equal? And when are EmpCodes equal?
If you think they are not default, or might change in future, consider to add properties to the EmployeeSalaryComparer:
private static IEqualityComparer<string> NameComparer {get} = StringComparer.InvariantCultureIgnoreCase;
private static IEqualityComparer<string> EmpCodeComparer {get} = StringComparer.OrdinalIgnoreCase;
...
The check for equality will end like:
return IdComparer.Equals(x.Id, y.Id)
&& NameComparer.Equals(x.Name, y.Name)
&& EmpCodeComparer.Equals(x.EmpCode, y.EmpCode)
&& SalaryComparer.Equals(x.Salary, y.Salary)
&& DateComparer.Equals(x.DateOfSalary, y.DateOfSalary);
If company policy about names in future changes, then all you have to do is select a different name comparer. And if EmpCode "Boss" is the same as EmpCode "boss": only one place to change the code.
Of course, after spec changes you need to change your unit tests, so they will tell you automatically where you forgot to change the proper equality comparers.
GetHashCode
GetHashCode is used to quickly check for inequality. Keywords: quickly, and inequality. If two Hash codes are different, we know that the object are not equal. It is not the other way round: if two hash codes are equal, we don't know whether the objects are equal.
The hash code is meant to quickly throw away most unequal objects. For instance, in a Distinct method, it would be nice if you could quickly throw away 99% of the objects, so you only have to thoroughly check 1% of the objects for equality.
With EmployeeSalaries we know that if the Id is different, than the Salaries are not equal. It will seldom be that two EmployeeSalaries will have the same Id, but different EmpCode. So by checking the Id only, we throw away most unequal EmployeeSalaries.
How about this:
public override int GetHashCode (EmployeeSalary x)
{
if (x == null) return 9875578; // just a number for null salaries
return x.Id.GetHashCode();
}
Conclusion
We've discussed why it is better to return IEnumerable instead of ToList.
We've talked about methods to make your code reusable, easier to read, maintainable, easier to unit test
We've talked about equality comparers
We've used Distinct to solver you problem
I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();
I want to sort a C# list by word. Assume I have a C# list (of objects) which contains following words:
[{id:1, name: "ABC"},
{id:2, name: "XXX"},
{id:3, name: "Mille"},
{id:4, name: "YYY"},
{id:5, name: "Mill",
{id:6, name: "Millen"},
{id:7, name: "OOO"},
{id:8, name: "GGGG"},
{id:9, name: null},
{id:10, name: "XXX"},
{id:11, name: "mil"}]
If user pass Mil as a search key, I want to return all the words starting with the search key & then all the words which does not match criteria & have them sort alphabetically.
Easiest way I can think of is to run a for loop over the result set, put all the words starting with search key into one list and put the renaming words into another list. Sort the second list and them combine both the list to return the result.
I wonder if there is a smarter or inbuilt way to get the desired result.
Sure! You will sort by the presence of a match, then by the name, like this:
var results = objects.OrderByDescending(o => o.Name.StartsWith(searchKey))
.ThenBy(o => o.Name);
Note that false comes before true in a sort, so you'll need to use OrderByDescending.
As AlexD points out, the name can be null. You'll have to decide how you want to treat this. The easiest way would be to use o.Name?.StartsWith(searchKey) ?? false, but you'll have to decide based on your needs. Also, not all Linq scenarios support null propagation (Linq To Entities comes to mind).
This should do it, but there's probably a faster way, maybe using GroupBy somehow.
var sorted = collection
.Where(x => x.Name.StartsWith(criteria))
.OrderBy(x => x.Name)
.Concat(collection
.Where(x => !x.Name.StartsWith(criteria))
.OrderBy(x => x.Name))
You can try GroupBy like this:
var sorted = collection
.GroupBy(item => item.Name.StartsWith(criteria))
.OrderByDescending(chunk => chunk.Key)
.SelectMany(chunk => chunk
.OrderBy(item => item.Name));
Separate items into two groups (meets and doesn't meet the criteria)
Order the groups as whole (1st that meets)
Order items within each group
Finally combine the items
There's nothing C#-specific to solve this, but it sounds like you're really looking for algorithm design guidance.
You should sort the list first. If this is a static list you should just keep it sorted all the time. If the list is large, you may consider using a different data structure (Binary Search Tree, Skip List, etc.) which is more optimized for this scenario.
Once it's sorted, finding matching elements becomes a simple binary search. Move the matching elements to the beginning of the result set, then return.
Add an indicator of a match into the select, and then sort on that:
void Main()
{
word[] Words = new word[11]
{new word {id=1, name= "ABC"},
new word {id=2, name= "XXX"},
new word {id=3, name= "Mille"},
new word {id=4, name= "YYY"},
new word {id=5, name= "Mill"},
new word {id=6, name= "Millen"},
new word {id=7, name= "OOO"},
new word {id=8, name= "GGGG"},
new word {id=9, name= null},
new word {id=10, name= "XXX"},
new word {id=11, name= "mil"}};
var target = "mil";
var comparison = StringComparison.InvariantCultureIgnoreCase;
var q = (from w in Words
where w.name != null
select new {
Match = w.name.StartsWith(target, comparison)?1:2,
name = w.name})
.OrderBy(w=>w.Match).ThenBy(w=>w.name);
q.Dump();
}
public struct word
{
public int id;
public string name;
}
It is probably not easier but you could create a class that implements IComparable Interface and have a property Mil that is used by CompareTo.
Then you could just call List.Sort(). And you can pass an IComparer to List.Sort.
It would probably be the most efficient and you can sort in place rather than producing a new List.
On average, this method is an O(n log n) operation, where n is Count;
in the worst case it is an O(n ^ 2) operation.
public int CompareTo(object obj)
{
if (obj == null) return 1;
Temperature otherTemperature = obj as Temperature;
if (otherTemperature != null)
{
if(string.IsNullOrEmpty(Mil)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill) && otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(!this.Name.StartsWith(Mill) && !otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill))
return 1;
else
return 0;
}
else
throw new ArgumentException("Object is not a Temperature");
}
You will need to add how you want null Name to sort
First create a list of the words that match, sorted.
Then add to that list all of the words that weren't added to the first list, also sorted.
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(words.Where(word => word.Name.StartsWith(keyword))
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}
Some of the comments suggest that it should be case-insensitive. That would be
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(
words.Where(word => word.Name.StartsWith(keyword, true)) //<-- ignoreCase
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}
I have a list of parameters like this:
public class parameter
{
public string name {get; set;}
public string paramtype {get; set;}
public string source {get; set;}
}
IEnumerable<Parameter> parameters;
And a array of strings i want to check it against.
string[] myStrings = new string[] { "one", "two"};
I want to iterate over the parameter list and check if the source property is equal to any of the myStrings array. I can do this with nested foreach's but i would like to learn how to do it in a nicer way as i have been playing around with linq and like the extension methods on enumerable like where etc so nested foreachs just feel wrong. Is there a more elegant preferred linq/lambda/delegete way to do this.
Thanks
You could use a nested Any() for this check which is available on any Enumerable:
bool hasMatch = myStrings.Any(x => parameters.Any(y => y.source == x));
Faster performing on larger collections would be to project parameters to source and then use Intersect which internally uses a HashSet<T> so instead of O(n^2) for the first approach (the equivalent of two nested loops) you can do the check in O(n) :
bool hasMatch = parameters.Select(x => x.source)
.Intersect(myStrings)
.Any();
Also as a side comment you should capitalize your class names and property names to conform with the C# style guidelines.
Here is a sample to find if there are match elements in another list
List<int> nums1 = new List<int> { 2, 4, 6, 8, 10 };
List<int> nums2 = new List<int> { 1, 3, 6, 9, 12};
if (nums1.Any(x => nums2.Any(y => y == x)))
{
Console.WriteLine("There are equal elements");
}
else
{
Console.WriteLine("No Match Found!");
}
If both the list are too big and when we use lamda expression then it will take a long time to fetch . Better to use linq in this case to fetch parameters list:
var items = (from x in parameters
join y in myStrings on x.Source equals y
select x)
.ToList();
list1.Select(l1 => l1.Id).Intersect(list2.Select(l2 => l2.Id)).ToList();
var list1 = await _service1.GetAll();
var list2 = await _service2.GetAll();
// Create a list of Ids from list1
var list1_Ids = list1.Select(l => l.Id).ToList();
// filter list2 according to list1 Ids
var list2 = list2.Where(l => list1_Ids.Contains(l.Id)).ToList();
I understand how to do a Distinct() on a IEnumerable and that I have to create an IEqualityComparer for more advanced stuff however is there a way in which you can tell which duplicated item to return?
For example say you have a List<T>
List<MyClass> test = new List<MyClass>();
test.Add(new MyClass {ID = 1, InnerID = 4});
test.Add(new MyClass {ID = 2, InnerID = 4});
test.Add(new MyClass {ID = 3, InnerID = 14});
test.Add(new MyClass {ID = 4, InnerID = 14});
You then do:
var distinctItems = test.Distinct(new DistinctItemComparer());
class DistinctItemComparer : IEqualityComparer<MyClass> {
public bool Equals(MyClass x, MyClass y) {
return x.InnerID == y.InnerID;;
}
public int GetHashCode(MyClassobj) {
return obj.InnerID.GetHasCode();
}
}
This code will return the classes with ID 1 and 3. Is there a way to return the ID matches 2 & 4.
I don't believe it's actually guaranteed, but I'd be very surprised to see the behaviour of Distinct change from returning items in the order they occur in the source sequence.
So, if you want particular items, you should order your source sequence that way. For example:
items.OrderByDescending(x => x.Id)
.Distinct(new DistinctItemComparer());
Note that one alternative to using Distinct with a custom comparer is to use DistinctBy from MoreLINQ:
items.OrderByDescending(x => x.Id)
.DistinctBy(x => x.InnerId);
Although you can't guarantee that the normal LINQ to Objects ordering from Distinct won't change, I'd be happy to add a guarantee to MoreLINQ :) (It's the only ordering that is sensible anyway, to be honest.)
Yet another alternative would be to use GroupBy instead - then for each inner ID you can get all the matching items, and go from there.
You don't want distinct then - you want to group your items and select the "maximum" element for them, based on ID:
var distinctItems = test.Distinct(new DistinctItemComparer());
var otherItems = test.GroupBy(a => a.InnerID, (innerID, values) => values.OrderBy(b => b.ID).Last());
var l1 = distinctItems.ToList();
var l2 = otherItems.ToList();
l1 = your current list
l2 = your desired list
This doesn't sound like a job for Distinct, this sounds like a job for Where. You want to filter the sequence in your case:
var ids = new[] { 2, 4 };
var newSeq = test.Where(m => ids.Contains(m.ID));
If you want to select one particular of the group of elements that are considered equal using the comparison you use, then you can use group by:
var q = from t in tests
group t by t.InnerID into g
select g.First(...);
In the select clause, you'll get a collection of elements that are equal and you can select the one specific element you need (e.g. using First(...)). You actually don't need to add Distinct to the end, because you're already selecting only a single element for each of the groups.
No, there's no way.
Distinct() is used to find distinct elements. If you're worried about which element to return...then obviously they are not truly identical (and therefore not distinct) and you have a flaw in your design.