List pattern matching for strings with specific StringComparison? - c#

I have a case when there is a list of some string. I want to check whether there are exactly 4 items and whether item 0 and item 2 have some value.
But when I write this code...
var a = new List<string>();
if(a is ["aSdfüü", _, "1.1", _] test){
// do something
}
... it is compiled to this:
List<string> list = new List<string>();
if (list != null && list.Count == 4 && list[0] == "aSdfüü")
{
bool flag = list[2] == "1.1";
}
So, the strings are compared with "==" and I can't find a way to specify a StringComparison like StringComparison.OrdinalIgnoreCase or similar.
Is there an elegant way to combine list pattern matching and a specific string comparison?

Related

C# for comparing two lists that may contain null values

I am comparing two excel sheets using C#.
I have assigned the sheets to datatables and I hope to compare the values in each cell. I have converted the datatables to lists and I'm iterating through them comparing them. How do I avoid a NullReferenceException if one of the values contains a null?
I am hitting a NullReferenceException at this point in the code:
if (lst1[i].ToString() != lst2[j].ToString())
Is there a way to guard against nulls here or do I need to handle null values earlier in the code?
Thanks
One good way to do this is to ask if the value is null before call .ToString().
So, would be like:
if (lst1[i] != null && lst2[j] != null && lst1[i].ToString() != lst2[j].ToString())
if (lst1[i]?.ToString() != lst2[j]?.ToString())
More info on the ?. operator
Just test for null before dereferencing lst1[i] and lst2[j]
if(lst1[i] is not null and lst2[j] is not null)
{
if (lst1[i].ToString() != lst2[j].ToString())
{
...
}
}
Assuming
if both items are null then they are equal;
if both items are not null and their string representation are equal then they are equal;
in any other case they are not equal;
you can make comparison of your lists more readable With following LINQ query.
var query = list1
.Zip(list2, (a, b) => new { a, b })
.Any(m => !((m.a == null && m.b == null) || (m.a != null && m.b != null && m.a.ToString() == m.b.ToString())));
if (query)
{
Console.WriteLine("Lists are not equal.");
}
else
{
Console.WriteLine("Lists are equal.");
}
Since you're doing ToString() it looks like both lists are typed as objects. Since they're coming from Excel they're presumably all either strings or simple value types.
In that case you can do
if(lst1.SequenceEquals(lst2))
That compares each item in both lists and ensure that they're equal. It accounts for nulls. You wouldn't need to compare each item one at a time. This compares the entire list. I'd write unit tests to make sure it doesn't throw any curves.
If for some reason you wanted to do a text-based comparison ("2" == 2) then you could create a comparer like this:
public class ObjectTextComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return x?.ToString() == y?.ToString();
}
public int GetHashCode(object obj)
{
return obj?.GetHashCode() ?? 0;
}
}
and then this would return true:
var x = new object[] { "x", 2, null, "y" };
var y = new object[] { "x", "2", null, "y" };
var comparer = new ObjectTextComparer();
var listsAreEqual = x.SequenceEqual(y, comparer);
That second option is a can of worms, though, if the values in one list are stored in Excel as text and the other ones as numbers or dates.
For example if one list contains a date and the other list contains the same date stored as text, the comparer is going to convert the first one to a string, but it might not be formatted the same way as the other one stored as text, and the two strings won't match.

Predicate filtration of collection in c# with LINQ and necessity to compare results with List<string> elements

I am filtering in C# with LINQ my collection according to below given code. It is working for my purpose at first good. But I have a situation that in the filtering I have to compare one of the fields of my object (ProductNo) with values from List. As long as I know how many elements will have this table I can do this. But I want to extend functionality so that my code will automatically check how many times should check this statement:
((ProductList)item).ProductNo.Contains(foundPT[i])
Please help me I was looking for solutions and the closest solution which I found was this one:
How to find if a string contains any items of an List of strings?
But I cannot implement it in my case.
static public ICollectionView ProductionTravelersFilter(ObservableCollection<ProductList> obProductList, string searchedString)
{
// Collection which will take ObservableCollection
var _itemSourceList = new CollectionViewSource() { Source = obProductList };
// ICollectionView the View/UI part
ICollectionView Itemlist = _itemSourceList.View;
List<string> foundPT = FilteredProductionTravelers(searchedString);
int arrayLength=foundPT.Count;
// your Filter
var CustomFilter = new Predicate<object>(item =>
((ProductList)item).TakenFromProductNo.Contains(searchedString) ||
((ProductList)item).DrawingNumber.Contains(searchedString) ||
((ProductList)item).ProductType.Contains(searchedString) ||
((ProductList)item).Material.Contains(searchedString) ||
((ProductList)item).CreationDate.Contains(searchedString) ||
((ProductList)item).ProductNo.Contains(searchedString) ||
((ProductList)item).Application.Contains(searchedString) ||
((ProductList)item).Description.Contains(searchedString)||
((ProductList)item).ProductNo.Contains(foundPT[0]) ||
((ProductList)item).ProductNo.Contains(foundPT[1]) ||
((ProductList)item).ProductNo.Contains(foundPT[2]) ||
);
//add our Filter
Itemlist.Filter = CustomFilter;
return Itemlist;
}

Get matched and unmatched elements from 2 lists

I have a Class named Privilegeswith the following properties int UserId,string FormName,string CompName,int Privilege
And I have 2 lists of Privileges type with different values as the sample below
List<Privileges> list1 = new List<Privileges>(){
new Privileges(){UserId= 1,FormName="Form1",CompName="Button1",Privilege=2},
new Privileges(){UserId= 2,FormName="Form1",CompName="Button3",Privilege=3},
new Privileges(){UserId= 3,FormName="Form2",CompName="Button2",Privilege=2}
};
List<Privileges> list2 = new List<Privileges>(){
new Privileges(){UserId= 5,FormName="Form1",CompName="Button1",Privilege=2},
new Privileges(){UserId= 2,FormName="Form1",CompName="Button3",Privilege=4},
new Privileges(){UserId= 4,FormName="Form2",CompName="Button2",Privilege=3}
};
I want to make 3 functions
I made the first one which returns matched elements between the 2 lists
and the result is the following
{UserId= 2,FormName="Form1",CompName="Button3",Privilege=3}
The 2nd function should return elements that exist in the first list and not in the second list, with the following result
{UserId= 1,FormName="Form1",CompName="Button1",Privilege=2},
{UserId= 3,FormName="Form2",CompName="Button2",Privilege=2}
The 3rd function should return elements that exist in the second list and not in the first list, with the following result
{UserId= 5,FormName="Form1",CompName="Button1",Privilege=2},
{UserId= 4,FormName="Form2",CompName="Button2",Privilege=3}
The matching clause should compare UserId,FormName,CompName values regardless what the value of privilege is.
you can check my code snippet here
You don't have to write any complex LINQ statements for these (and many more) tasks. Just define an IEqualityComparer and everything becomes almost ridiculously simple:
class PrivilegesComparer : IEqualityComparer<Privileges>
{
public bool Equals(Privileges x, Privileges y)
{
return x.UserId == y.UserId
&& x.FormName == y.FormName
&& x.CompName == y.CompName;
}
public int GetHashCode(Privileges obj)
{
return (obj.UserId + obj.FormName + obj.CompName).GetHashCode();
}
}
Usage:
var comparer = new PrivilegesComparer();
var intersect = list1.Intersect(list2, comparer);
var l1Exceptl2 = list1.Except(list2, comparer);
var l2Exceptl1 = list2.Except(list1, comparer);
Which represent your first, second and third function, respectively.
That's quite different from writing a complex LINQ statement for each individual task.
Elements in list1 not in list2
var itemsInList1NotInList2 = list1.Where(l1 => !list2.Any(l2 => l1.UserId == l2.UserId && l1.FormName == l2.FormName && l1.CompName == l2.CompName)).ToList();
Elements in list2 not in list1
var itemsInList2NotInList1 = list2.Where(l2 => !list1.Any(l1 => l1.UserId == l2.UserId && l1.FormName == l2.FormName && l1.CompName == l2.CompName)).ToList();

Create Dictionary and use key in value expression declaration

I have two lists and would like to create a dictionary of key value pair type
Key = string of each unique reference (of type string)
Value = sub list of list2 where list2 uniquereference are equal to Key (the value is of type List)
I want to do it in essense like this
var dictionary = listA.ToDictionary(x => x.StringProperty, y => List2.Where(x => x.Reference == x.StringProperty)
How can I do this in a expression declaration? As I currently have a method to create this dictionary?
I had to create this method to generate what I wanted. Is there an expression that will do the same thing:
private Dictionary<string, IEnumerable<JobEntity>> Dictionary(IEnumerable<JobLinkedBag> jobLinkedBags, IEnumerable<JobEntity> jobs)
{
if (jobLinkedBags == null || jobs == null || jobLinkedBags.Count() == 0 || jobs.Count() == 0)
return null;
var dictionary = new Dictionary<string, IEnumerable<JobEntity>>();
for (int i = 0; i < jobLinkedBags.Count(); i++)
{
var thisBag = jobLinkedBags.ElementAt(i).LuggageCode;
var jobsForThisBag = jobs.Where(x => x.LuggageCode == thisBag);
dictionary.Add(thisBag, jobsForThisBag);
}
return dictionary;
}
The 1:1 translation of your method into an expression would look like this:
Expression<Func<IEnumerable<JobLinkedBag>, IEnumerable<JobEntity>, Dictionary<string, IEnumerable<JobEntity>>>> expression =
(jobLinkedBags, jobs) => (jobLinkedBags == null || jobs == null || jobLinkedBags.Count() == 0 || jobs.Count() == 0)
? null
: jobLinkedBags.ToDictionary(
jlb => jlb.LuggageCode,
jlb => jobs.Where(job => job.LuggageCode == jlb.LuggageCode));
Note that there are some dangerous parts in this code:
this codes enumerates the arguments jobLinkedBags and jobs several times (to determine the count and then to filter them), this may cause problems depending on what specific IEnumerable implementation they are
you build a dictionary that has IEnumerable as value. This enumeration is also executed defered. So you should make sure that the sources of this enumeration are still valid when you eventually will iterate through it.

FindAll in a c# List, but varying search terms

List<DTOeduevent> newList = new List<DTOeduevent>();
foreach (DTOeduevent e in eduList.FindAll(s =>
s.EventClassID.Equals(cla)
&& s.LocationID.Equals(loc)
&& s.EducatorID.Equals(edu)))
newList.Add(e);
cla, loc, edu can be (null or empty) or supplied with values--
basically how can I simply return the original list (eduList) if cla, loc, edu are all null
or search by loc, search by loc, edu, search by edu, cla -- etc........
my sample code only makes a new list if all 3 vars have values--
is there an elegant way to do this, without brute force if statements?
List<DTOeduevent> newList = eduList.FindAll(s =>
(cla == null || s.EventClassID.Equals(cla))
&& (loc == null || s.LocationID.Equals(loc))
&& (edu == null || s.EducatorID.Equals(edu)));
Assuming the values are Nullable value types or classes. If they're strings, you could replace cla == null with String.IsNullOrEmpty(cla).
IEnumerable<DTOeduevent> newList = eduList;
if (cla != null)
{
newList = newList.Where(s => s.EventClassID == cla);
}
if (loc != null)
{
newList = newList.Where(s => s.LocationID == loc);
}
if (edu != null)
{
newList = newList.Where(s => s.EducatorID == edu);
}
newList = newList.ToList();
Due to deferred execution, the Where statements should all execute at once, when you call ToList; it will only do one loop through the original list.
I would personally lean towards something that encapsulated the logic of what you seem to be doing here: checking that a found id is equal to some search id. The only wrinkle is how to get that check for null or empty in there first.
One way to do that is by using a static extension method:
public static class DtoFilterExtensions
{
public static bool IsIdEqual(this string searchId, string foundId) {
Debug.Assert(!string.IsNullOrEmpty(foundId));
return !string.IsNullOrEmpty(searchId) && foundId.Equals(searchId);
}
}
I would also lean towards using LINQ and IEnumerable<> as Domenic does, even though you could make it work with List.FindAll just as easily. Here would be a sample usage:
public void Filter(string cla, string loc, string edu) {
var startList = new List<DTOeduevent>();
var filteredList = startList
.Where(x => x.classId.IsIdEqual(cla) && x.locationId.IsIdEqual(loc) && x.educatorId.IsIdEqual(edu));
Show(filteredList.ToList());
}
In your own code of course you have got that start list either in a member variable or a parameter, and this assumes you have got some method like Show() where you want to do something with the filtered results. You trigger the deferred execution then, as Domenic explained with the ToList call (which is of course another extension method provided as part of LINQ).
HTH,
Berryl

Categories

Resources