Using IEnumerable FirstOrDefault within a List extension method? - c#

Is it possible to use IEnumerable FirstOrDefault within a List extension method? I am getting the error 'List does not contain a definition for FirstOrDefault'?
using LanguageExt;
using System;
using System.Linq;
public static class Question
{
public static Option<T> Lookup<T>(this List<T> enumerable, Func<T, bool> predicate)
{
if (enumerable == null)
{
return Option<T>.None;
}
var val = enumerable.FirstOrDefault(predicate);
return val == null ? Option<T>.None : Option<T>.Some(val);
}
public static void Run()
{
bool isOdd(int i) => i % 2 == 1;
var none = new List<int>().Lookup(isOdd); // => None
var some = new List<int> { 1 }.Lookup(isOdd); // => Some(1)
}
}

Normally it is possible to use FirstOrDefault on any enumerable sequence, so also on Lists.
The following works:
Func<int, bool> predicate = isOdd;
List<int> integerSequence = new List<int>();
var a = integerSequence.FirstOrDefault();
var b = integerSequence.Where(i => predicate(i)).FirstOrDefault();
var c = integerSequence.FirstOrDefault(i => predicate(i));
So I can't reproduce your problem. I see other problems though.
If you don't understand the this keyword, read Extension Methods demystified. Whenever you think: damn, I wish they had thought of this LINQ method, consider creating an extension method.
So your Lookup method is an extension method that takes a list and a predicate as input.. Since it seems that it can work on any enumerable sequence, let's not limit ourselves to List<T>, let's accept any enumerable sequence, like Arrays, Dictionaries, Lookup-tables, etc.
Furthermore, LINQ methods are most reusable if you let them return a sequence or result items, and let your called decide whether he wants all items, or only the FirstOrDefault, or maybe the Last, or Average.
So if you decide to create an extension method that takes an enumerable sequence as input, let it return an Enumerable sequence whenever possible, even if the sequence is empty: avoid returning null, if you mean: there are no elements matching what you want. Because that would mean that callers should have to check the result before they can continue concatenating other LINQ methods.
Finally: LINQ methods usually don't except null sources as input, they throw exceptions. LINQ methods expect callers to make sure the input is not null. Of course you are free to deviate from this, but callers don't expect it when using LINQ like methods.
With these guidelines in mind, consider to change your extension method.
Apparently, if an element in your input sequence equals null, you want to return Option<T>.None, otherwise you want Option<T>.Some(val)
public static Option<T> ToOption<T>(this T sourceElement)
{
return sourceElement?.Option<T>.Some(sourceElement) ?? Option<T>.None;
}
In words: if sourceElement equals null, return Option<T>.Some(sourceElement), otherwise return Option<T>.None
In LINQ it is quite normal to create extension methods with equality comparers, this will make your methods even more reusable. Usually this will only need two or three lines of code:
public static Option<T> ToOption<T>(this T sourceElement)
{
return ToOption(sourceElement, null);
}
public static Option<T> ToOption<T>(
this T sourceElement,
IEqualityComparer<T> comparer)
{
if (comparer == null) comparer = EqualityComparer<T>.Default;
if (comparer.Equals(sourceElement, null)
return Option<T>.None;
else
return Option<T>.Some(sourceElement);
}
In the methods below I won't mention this possibility over and over again.
public static IEnumerable<Option<T>> ToOptions<T>(this IEnumerable<T> source)
{
// TODO: throw exception if input null
return source.Select(sourceElement => sourceElement.ToOption());
}
public static IEnumerable<Option<T>> ToOptions<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
{
// No need: check if source == null, this is done in the other ToOptions
// TODO: exception if predicate equals null
return source.Where(sourceElement => predicate(sourceElement))
.ToOptions();
}
Simple usage:
IEnumerable<Customer> customers = ...
MyClass myFirstEligableCustomer= customers
.ToOptions(customer => customer.Address.City == "New York")
.FirstOrDefault();
Or intertwined with other LINQ methods:
var result = customers.
.GroupBy(customer => customer.Birthday.Year)
.OrderBy(customerGroup => customerGroup.Key)
.ToOptions(customerGroup => customerGroup.Key)
.ToDictionary(customerGroup => customerGroup.Key, // Key
customerGroup => customerGroup.ToList); // Value
The result is a Dictionary, where the key is the Year of a birthday, and the value is the list of all Option<Customer> that are born in this year.
You see: most methods are barely more than one-liners. They are easy to reuse, easy to change, easy to test.

Related

Generic Query With PredicateBuilder in Linqkit

I've been using LinqKit to create generic queries for quite some time.
One thing that has always bothered me is the fact that you always have to test whether the value sent in the filter is valid.
For example: Suppose I have a string filter. Conditions can be Equal, StartsWith, EndsWith and Contains.
My method would look something like this:
public List<MyModel> Get(MyModelFilter filter)
{
if (string.IsNullOrEmpty(filter.prop))
{
predicate = predicate.And(_myModel => myModel.Prop.Contains(filter.prop));
}
// Plus a giant amount of if's with multiple filters
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
To end this bunch of If's, I decided to create a generic method to apply the filter to the properties.
My idea is to pass the property where the filter will be applied, and the filter definition, and encapsulate the Expression creation logic
It would be something of the type:
public List<MyModel> Get(MyModelFilter filter)
{
predicate = predicate.And(_myModel => myModel.Prop, filter.PropFilterDefinition);
// Goodnye If's, Only others filter impl
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
For this, I've created some extension methods to handle this
public static Expression<Func<TPredicate, bool>> And<TPredicate>(
this ExpressionStarter<TPredicate> predicate,
Func<TPredicate, string> property, StringFilterDefinition filter,
bool ignoreNull = true)
{
if (InvalidStringFilter(filter, ignoreNull))
{
return predicate;
}
// This is LinqKit's And Extension Method
return predicate.And(BuildPredicate(property, filter));
}
private static Expression<Func<TPredicate, bool>> BuildPredicate<TPredicate>(
Func<TPredicate, string> property,
StringFilterDefinition filter)
{
if (filter.Filter == StringFilterComparators.Equal)
{
return x => property.Invoke(x) == filter.Value;
}
if (filter.Filter == StringFilterComparators.BeginsWith)
{
return x => property.Invoke(x).StartsWith(filter.Value);
}
if (filter.Filter == StringFilterComparators.EndsWith)
{
return x => property.Invoke(x).EndsWith(filter.Value);
}
return x => property.Invoke(x).Contains(filter.Value);
}
private static bool InvalidStringFilter(
StringFilterDefinition filter,
bool ignoreNullValue = true)
{
if (filter?.Filter == null)
{
return true;
}
return ignoreNullValue && string.IsNullOrEmpty(filter.Value);
}
The problem is that the filter is not applied, and the answer is in Invoke right up there. EF can not translate the above expression to SQL.
The EF error is
Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryCompilationContextFactory[8]
The LINQ expression '(__property_0.Invoke([x]) == __filter_Value_1)'
could not be translated and will be evaluated locally. To configure
this warning use the DbContextOptionsBuilder.ConfigureWarnings API
(event id 'RelationalEventId.QueryClientEvaluationWarning').
ConfigureWarnings can be used when overriding the
DbContext.OnConfiguring method or using AddDbContext on the
application service provider.
The question is:
How can I make this construction work?
Also, any suggestions on how best this?
You seem to forgot that besides the PredicateBuilder, the really useful feature provided by LINQKit AsExpandable, Expand and Invoke custom extension methods is to be able to correctly embed expressions inside the expression tree.
In order to utilize that feature, you should use Expression<Func<...>> instead of Func<...>. In the posted code, replace all occurrences of Func<TPredicate, string> with Expression<Func<TPredicate, string>> and the issue should be solved.

C# LINQ Generic Extension Method to check if sequence is the same and return it's value

I am trying to put together an extension method that will allow me to check if all values of a sequence are the same, and if so return that value, or if not return an average of that value if its an int or double or return a concatenation of that value if it is a string.
For example take the following object and a list
public class Item
{
public int Size {get; set;}
public string Name {get; set;}
}
var itemsTheSame = new List<Item>
{
new Item { Size = 1, Name = "Red" },
new Item { Size = 1, Name = "Red" },
new Item { Size = 1, Name = "Red" }
};
I want to be able to do something like this
itemsTheSame.AllEqual(item => item.Size)); // should return 1 as an int
itemsTheSame.AllEqual(item => item.Name)); // should return "Red" as a string
My generics are not very strong, but this is what ive started off with
public static class Extensions
{
public static int AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, int> selector)
{
if (!list.Any()) return default(int);
var first = list.First();
return list.Skip(1).All(selector == first) ? default(int) : default(int);
}
}
It's quite obvious to me that the return type needs to be generic aswell, so came up with this (doesent compile)
public static TReturn AllEqual<TSource, TReturn>(this IEnumerable<TSource> list, Func<TSource, TReturn> selector)
{
if (!list.Any()) return default(TReturn);
var first = list.First(selector);
return list.Skip(1).All(selector == first) ? default(TReturn) : default(TReturn);
}
I am completely confused as I need to be able to pass Func<TSource, TReturn> selector to the .First() to be able to get the value itself, but .First needs a Func<TSource, bool>. I also need to be able to pass the same selector to .All as I only want to check the field specified in the selector for equality.
Is this even possible or am i better off having a set of overloaded methods, one that returns an int and the other that returns a string etc ?
Fiddle here
First doesn't accept a selector, it accepts a predicate. To apply a selector to the first item you need to get the first item (using its parameterless overload), and then invoke the selector on that result.
And to compare objects of an unknown type for equality you won't be able to use the ==operator. It is bound at compile time, so it couldn't account for the specific implementation for that type, in addition to the fact that it might not have one. You should use an IEqualityComparer<T> to compare the items for equality (you can use the default comparer if none is provided).
Also note that your implementation iterates the source enumerable up to 3 times. That could easily be a problem if it's actually a query, and not an already materialized collection. You're really better off just iterating the sequence explicitly, rather than using LINQ operations here, since you can't do it with just one LINQ query.
Try to avoid iterating the IEnumerable multiple times. It could be too expensive, you never know.
You need an implementation for each numeric type because of the average feature.
Average is actually the same whether you have all the same values or not. Just use the Average extension method instead. You may want to wrap it to make the syntax the same. (Or consider to give it a different name, like AllEqualOrAverage, because it doesn't do the same.)
The average of an int is usually a double. Decide whether you want to round it, use floor or actually return a double.
You need Select(selector) to get the values from the items. The argument of First is a predicate, as in Where(predicate).
Proposal:
// for doubles (returning average or default if list is empty)
public static double AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, double> selector)
{
// use IList<T> interface to quickly check the size of the
// list if this interface is available. Use ToArray otherwise.
var ilist = list as IList<TSource> ?? list.ToArray();
if (ilist.Count == 0) return 0.0;
list.Select(selector).Average();
}
// for ints (returning average or default if list is empty)
public static double AllEqual<TSource>(this IEnumerable<TSource> list, Func<TSource, int> selector)
{
// use IList<T> interface to quickly check the size of the
// list if this interface is available. Use ToArray otherwise.
var ilist = list as IList<TSource> ?? list.ToArray();
if (ilist.Count == 0) return 0.0;
list.Select(selector).Average();
}
// for everything else (returning unique value or default)
public static TReturn AllEqual<TSource, TReturn>(this IEnumerable<TSource> list, Func<TSource, TReturn> selector)
{
// put 2 distinct values into an array
var distinctItems.Select(selector).Distinct().Take(2).ToArray();
if (distinct.Length == 1) return distinct[0];
default(TReturn);
}
Note that this only works if you know the numeric type at compile time. If you need to return the average of a number which is only recognized at runtime (it is an object at compile time), it gets really hard.

IEqualityComparer Exception with Linq (NotSupportedException)

I was doing a custom Comparer to compare two classes in a Linq query like this one:
Table<Info> table = context.GetTable<Info>();
bool infoAlreadyExists = table.Contains(info, new MyComparer());
This is my comparer:
public class MyComparer : IEqualityComparer<Info>
{
#region IEqualityComparer<Info> Member
public bool Equals(Info x, Info y)
{
return x.Content == y.Content;
}
public int GetHashCode(Info obj)
{
return obj.Content.GetHashCode();
}
#endregion
}
The problem is that I get an exception. [System.NotSupportedException]
The exception tells me that a not supported overload for the Contains operator was found. Do I do something wrong or is it really NotSupported? I couldn't find anything on the documentation.
This is the definition of the overload I am trying to use of the contains method.
public static bool Contains<TSource>(this IQueryable<TSource> source, TSource item, IEqualityComparer<TSource> comparer);
That version of Contains method is not supported.You can see the full list here:
Supported and Unsupported LINQ Methods (LINQ to Entities)
So you need to perform this operation in memory, you can use AsEnumerable for that.
But in this case it seems you don't need that equality comparer. You can just use the below query to get the same result:
table.FirstOrDefault(x => x.Content == info.Content) != null;

Searching a list of objects for a non empty property

I'm searching a list of objects for a certain property. I'm repeating this code for a lot of properties, so I'm trying to make the reading of the property as compact as possible.
Here's what I currently have:
value = ReadValue(p => p.ProductCatalogId != 0, p => p.ProductCatalogId);
public T ReadValue<T>(Func<MyType, bool> predicate, Func<MyType, T> selector)
{
return m_settingsPages.Where(predicate).Select(selector).FirstOrDefault();
}
I always compare against the default value for the type, and always for inequality. I would like to remove the predicate argument. Can I use partial application or a similar technique to get rid of the predicate argument?
Pseudo code:
value = ReadFirstValue(p => p.ProductCatalogId);
public T ReadFirstValue<T>(Func<MyType, T> selector) where T : IEquatable<T>
{
var predicate = selectorToPredicate(selector); //Compare with default(T) for non equality
return m_settingsPages.Where(predicate).Select(selector).FirstOrDefault();
}
How would selectorToPredicate look and how would I call it?
Sounds like you could do something as simple as:
Func<MyType, bool> SelectorToPredicate<T>(Func<MyType, T> selector)
{
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
return x => !comparer.Equals(selector(x), default(T));
}
One thing to note - if your property is a string property, this will return empty strings. Is that what you want?

Appending an element to a collection using LINQ

I am trying to process some list with a functional approach in C#.
The idea is that I have a collection of Tuple<T,double> and I want to change the Item 2 of some element T.
The functional way to do so, as data is immutable, is to take the list, filter for all elements where the element is different from the one to change, and the append a new tuple with the new values.
My problem is that I do not know how to append the element at the end. I would like to do:
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
return collection.Where(x=>!x.Item1.Equals(term)).Append(Tuple.Create(term,value));
}
But there is no Append method. Is there something else?
I believe you are looking for the Concat operator.
It joins two IEnumerable<T> together, so you can create one with a single item to join.
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
var newItem = new List<Tuple<T,double>>();
newItem.Add(new Tuple<T,double>(term,value));
return collection.Where(x=>!x.Item1.Equals(term)).Concat(newItem).ToList();
}
It seems that .NET 4.7.1 adds Append LINQ operator, which is exactly what you want. Unlike Concat it takes a single value.
By the way, if you declare a generic method you should include type parameter(s) after its name:
public List<Tuple<T, double>> Replace<T>(List<Tuple<T, double>> collection, T term, double value)
{
return collection.Where(x => !x.Item1.Equals(term))
.Append(Tuple.Create(term, value))
.ToList();
}
LINQ is not for mutation.
Functional programming avoid mutation.
Thus:
public IEnumerable<Tuple<T,double>> Extend(IEnumerable<Tuple<T,double>> collection,
T term,double value)
{
foreach (var x in collection.Where(x=>!x.Item1.Equals(term)))
{
yield return x;
}
yield return Tuple.Create(term,value);
}
If you're willing to use an additional package, check out MoreLinq, available on Nuget. This provides a new overload to the Concat-Function:
public static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail);
This function does exactly what was asked for, e.g. you could do
var myEnumerable = Enumerable.Range(10, 3); // Enumerable of values 10, 11, 12
var newEnumerable = myEnumerable.Concat(3); // Enumerable of values 10, 11, 12, 3
And, if you like LINQ, you will probably like a lot of the other new functions, too!
Additionally, as pointed out in a discussion on the MoreLinq Github-page, the function
public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element);
with a different name but the same functionality is available in .NET Core, so it might be possible that we will see it for C# in the future.
This should do what you want (although it uses mutation inside, it feels functional from a callers perspective):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
var result = collection.Where(x => !x.Item1.Equals(term)).ToList();
result.Add(Tuple.Create(term, value));
return result;
}
A alternative way to do it is to use "map" (select in LINQ):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
return collection.Select(x =>
Tuple.Create(
x.Item1,
x.Item1.Equals(term) ? value : x.Item2)).ToList();
}
But it might give you different results than your original intention. Although, to me, that's what I think when I see a method called Replace, which is, replace-in-place.
UPDATE
You can also create what you want like this:
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
return collection.
Where(x => !x.Item1.Equals(term)).
Append(Tuple.Create(term, value)).
ToList();
}
Using Concat, as mentioned by Oded:
public static class EnumerableEx {
public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) {
return source.Concat(new T[] { item });
}
}
One way is to use .Concat(), but you need to have a enumerable rather than a single item as the second argument. To create an array with a single element does work, but is combersome to write.
It is better to write an custom extension method to do so.
One method is to create a new List<T> and add the items from the first list and then the items from the second list. However, it is better to use the yield-keyword instead, so you do not need to create an list and the enumerable will be evaluated in a lazy fashion:
public static class EnumerableExtensions
{
public static IEnumerable<T> Concat<T>(this IEnumerable<T> list, T item)
{
foreach (var element in list)
{
yield return element;
}
yield return item;
}
}
The closest answer I could find came from this post and is:
return collection.Where(x=>!x.Item1.Equals(term)).Concat(new[]{Tuple.Create(term,value)});

Categories

Resources