What is the collection version of SingleOrDefault for a Dictionary<T>? - c#

Title kind of says it all. I just can't seem to find a DictionaryOrDefault \ ListOrDefault \ CollectionOrDefault option.
Is there such a method? If not how do I do this:
MyClass myObject = MyDictionary
.SingleOrDefault(x =>
{
if (x.Value != null)
return (x.Value.Id == sourceField.SrcField.Id);
else
return false;
}).Key;
if there is more than one match? (I am getting an execption because SingleOrDefault is only meant for single results (imagine that!).)
Guess I needed to be clearer (though the where answers looks good).
I have the above statement. I changed my program so that it does not always return 1 (there can be several values that match one key). That fails so I am looking for a collection to be returned (rather than just one item).

You can use IEnumerable<T>.FirstOrDefault(Func<T, bool> predicate) if your intention is to return the first one matching the predicate.
Otherwise you're simply looking at the IEnumerable<T>.Where(Func<T, bool> predicate) linq extension, which will return all elements that match the predicate passed. That will return an empty IEnumerable<T> if no elements match the predicate, at which point if you really need the value to be null, you can just look if anything is in it.
var res = MyDictionary.Where(x =>
{
if (x.Value != null)
return (x.Value.Id == sourceField.SrcField.Id);
return false;
});
if (!res.Any())
res = null;
Then if you absolutly need to have it as a list, you can just call
res.ToList();
Note that if you're actually manipulating a Dictionary<TKey, TValue>, res will contain KeyValuePair<TKey, TValue>'s.

if you do something like
var mylist = obj.Where(x=>x.attr1 == 4);
you can then check if anything was returned using the .Any() method
mylist.Any()

Related

Can I use Strongly type parameters based on properties from Object

I have a list of objects.
I have a single item with same type as the list.
I would like to create a generic extension method (called Find) to find an item from the list based on the single obj as well as an arbitrary list of its strongly typed properties.
Here is how I would like to call the method:
var obj = new SomeObject() { ... } ;
var list = new List<SomeObject>() { ... };
// Find similar objects
list.Find(obj, x => x.Id,y => y.Description);
Is this arrangement possible?
FirstOrDefault would work as in the comment below. However, I am looking for a way to use the pattern in different scenarios that might not be a simple lookup.
You can do this
public static T Find<T>(this IEnumerable<T> source, params Func<T,bool>[] condition)
{
return source.FirstOrDefault(o => condition.All(f => f(o))); // use All for && or use Any for ||
}
And to use it
var item = list.Find(x=> x.Id == obj.Id,x=> x.Description == obj.Description);
You can use Object if you want to make this work more generally by casting properties to object. but this will be a bit slower by the way and you must note that the Equals method for custom types must be overriden in order to make it work.
public static T Find<T>(this IEnumerable<T> source, T obj, params Func<T, object>[] condition)
{
return source.FirstOrDefault(o => condition.All(f => f(o).Equals(f(obj))));
}
Then you can call it exactly like this.
var item = list.Find(obj, x => x.Id,y => y.Description);
You can use Where if you want to return all similar items. by just changing FirstOrDefault into Where and the return type of method into IEnumerable<T>
It doesn't seem to me like you're saving a lot of time/effort versus just using a normal Where clause that filters on whichever properties you're after.
var findResults = list.Where(x => x.Id == obj.Id || x.Description == obj.Description);
Maybe you should consider proceeding like this:
var myResult = from currentObject in mylist
where currentObject == mySingleObject // change it by your matching criterias linked by && or || (as in if ...)
// NB: you can also override the == operator in your object definition
// but as mentionned below, it won't work for generic types
select currentObject;

How to Get a Object from IEnumerable collection using LINQ Lambda?

storageColl is having a IStorage with property "Id" as "Test".
What I am doing-
string id="Test";
IEnumerable<IStorageObject> storageColl = getStorageCollection();
IStorageObject storageObject = storageColl.ToList().Where(m => m.Properties["Id"] == id)
.ToList()
.Cast<IStorageObject>().ToArray()[0];
Is there a better way to do this. As this may throw array out of bound exception if the storageColl will not have that "Test".
You can use FirstOrDefault on the IEnumerable.
var storageObject = storageCol1.Where(m => m.Properties["Id"] == id).FirstOrDefault();
Or as David Hedlund pointed out, use the predicate overload on FirstOrDefault and remove the Where.
var storageObject = storageCol1.FirstOrDefault(m => m.Properties["Id"] == id);
Your storageColl is a sequence of objects that implement IStorageObject. The use of the Where only limits the elements you get when you enumerate over the sequence, it does not change them.
It is a waste of processing power to convert the sequence to a list when you only need the first element of the sequence or the a subset of it.
Familiarize yourself with the following Ling functions:
Any() returns true if the sequence contains at least one element
Any( item => ....) return true if any of the elements in the sequence meets the requirement
First() returns the first element of the sequence. Exception if not Any()
FirstOrDefault returns the first element of the sequence or the default (usually null) if not Any()
The nice thing about these functions is that they don't have to enumerate over all elements in the sequence, but can stop as soon as they found something.
If you use ToList() the code enumerates over all elements, throws most of them away and uses only the first element. FirstOrDefault() would have stopped after the first enumeration.
since the collection is implement IStorageObject you don't neet to cast them and for get item by index you can use any class that utilizes Array or IList
since LINQ operates on IEnumerable (Array itself is enumerable->to iterate) you don't need to cast them to array.you can utilize ElementAt method or Use IList classes (as List)
IStorageObject storageObject = storageColl.Where(m => m.Properties["Id"] == id).First();
You can simply achieve this by
var result = storageColl.Where(m => m.Properties["Id"] == id);
You should check firstOrDefault because can return null.
var Object = storageCol.Where(p => p.Properties["Id"] == id).FirstOrDefault();
if(Object != null)
{
// Do some Work
}

PredicateBuilder Returns No Rows

This code correctly returns one row:
_loadedAssemblies.ForEach(x =>
{
foundTypes.AddRange(from t in x.GetTypes()
where t.GetInterfaces().Contains(typeof(TInterface))
&& t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`')
select t);
}
However, when I use PredicateBuilder, I get zero rows:
var compiledPredicate = CompiledPredicate<TInterface>();
_loadedAssemblies.ForEach(x =>
{
foundTypes.AddRange(from t in x.GetTypes()
where compiledPredicate.Invoke(typeof(TInterface))
select t);
}
private static Func<Type, bool> CompiledPredicate<T>() where T : class
{
// True means all records will be returned if no other predicates are applied.
var predicate = PredicateBuilder.True<Type>();
// Get types that implement the interface (T).
predicate = predicate.And(t => t.GetInterfaces().Contains(typeof(T)));
// If the config file includes filtering by base class, then filter by it.
if (!string.IsNullOrWhiteSpace(_baseClass))
{
Type baseClass = Type.GetType(_baseClass);
predicate = predicate.And(t => t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`'));
}
return predicate.Compile();
}
Someone suggested creating a copy of my loop variable, but I tried that and I still get zero rows. I'm not sure why using PredicateBuilder returns no rows. Any idea what I'm missing?
The change that you mentioned in comments (foundTypes.AddRange(x.GetTypes().AsQueryable().Where(compiledPredicate));) had nothing at all to do with the fact that you were using AsQueryable. In the first case you're passing in a hard coded type to each call of your predicate, in the second you're passing the given item from the sequence. Had you removed the AsQueryable and used Enumerable.Where it would also work, or had you passed the current item, rather than a hard coded type, when invoking it that would also work.
So you can simply do:
foundTypes.AddRange(x.GetTypes().Where(compiledPredicate));
Also, when creating the predicate, there's no need to do as much work as you're doing. Expressions take a fair amount of extra work to deal with. With linq to objects you only need to deal with delegates, which are much less finicky.
private static Func<Type, bool> CompiledPredicate<T>() where T : class
{
Func<Type, bool> predicate = t => t.GetInterfaces().Contains(typeof(T));
if (!string.IsNullOrWhiteSpace(_baseClass))
{
Type baseClass = Type.GetType(_baseClass);
return t => predicate(t) &&
t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`');
}
return predicate;
}

Sequence contains more than one matching element

When I am trying to set IsDefault property of each dressing items where match a condition it throws an error saying:
Sequence contains more than one matching sequence.
(this.DressingItems
.Where(xx => xx.DressingInfo.CatID == catId
&& xx.ProductID == this.ProductID)
.Single()).IsDefault = false;
Well, this exception says that at least two items of the sequence DressingItems match your Where condition. The call to Single then causes the exception because it asserts that only one item is passed in.
Reading your question makes me think that you want to do something on each item of the input sequence, so you will probably use a foreach loop:
foreach(var item in this.DressingItems.Where(xx => xx.DressingInfo.CatID == catId && xx.ProductID == this.ProductID))
{
item.IsDefault = false;
}
this.DressingItems.Where(x=> x.DressingInfo.CatID == catId &&
x.ProductID == this.ProductID).ToList()
.ForEach(item=>item.IsDefault = false);
The point of the Single operator is to assert that a given sequence only has one item. For instance when retrieving a specific instance by primary key.
I suppose you want to mutate the state of any DressingItem matching the criteria, in which case you have some options, all involving enumerating the resultset, and executing some behavior.
There is no LINQ operator to specifically do this, since LINQ operators are meant to be pure. Pure functions are functions that do not have side effects, and this is exactly what you are trying to do.
There is, however, an extensionmethod on List<T> which does allow this. e.g.
this.DressingItems.Where(di => di.DressingInfo.CatID == catId
&& di.ProductID == this.ProductID)
.ToList()
.ForEach(di =>
{
di.IsDefault = false
});
Or you could roll your own:
public static class EnumerableExtensions
{
public static IEnumerable<T> ForEach<T>(
this IEnumerable<T> source,
Action<T> mutator)
{
var buffered = source.ToList();
buffered.ForEach(mutator);
return buffered;
}
}
You might ask why the guys at Microsoft decided against adding this to the BCL: As I recall, the idea was that an extensionmethod vs. a foreach() { } construct would not yield much benefits in terms of typing anyway, and it wouldn't help at all in terms of ambiguity. All other operators are side-effect free, and this one is explicitely designed to induce them.
It is an InvalidOperationException thrown by the Single method.
The method is supposed to return only one element, please check the criteria that you use on your query.
However an exception is also thrown when it fails to find any element
you have more than one item in this.DressingItems that match the given CatId and Product Id.
if you are sure there must be one (single), then you have to review how is this.DressingItems loaded.
if it's expected there are more than one, then you have to use foreach to set the values.
Since you are looking for a one liner, you could create your own method doing that.
public static void DoActionForEachElement<T>(IEnumerable<T> items, Func<T, bool> predicate, Action<T> action)
{
foreach (var item in items)
{
if (predicate(item))
action(item);
}
}
and then call it by
DoActionForEachElement(
DressingItems,
xx => xx.DressingInfo.CatID == catId && xx.ProductID == ProductID,
x => x.IsDefault = false);
This way you don't have to cast the result from Where to a List first.

Determine if an int value exists once in an array

I know you can use Any, Exists, and Single with LINQ but can't quite get this to work. I need to do a lookup based on an id to see if it's in the array and make sure that there is only ONE match on that value. because if there are 2 it's gonna cause an issue..the requirement that I'm checking is that the array only has one and only one of each ID in the array.
Here's what I tried
if(someIntArray.Single(item => item = 3)
//... we found the value 8 in the array only once so now we can be confident and do something
Here's how I would solve this:
if (someIntArray.Count(item => item == 3) == 1)
{
//only one '3' found in the array
...
}
I created a One() extension method set for just this situation:
public static bool One<T>(this IEnumerable<T> sequence)
{
var enumerator = sequence.GetEnumerator();
return enumerator.MoveNext() && !enumerator.MoveNext();
}
public static bool One<T>(this IEnumerable<T> sequence, Func<T, bool> predicate)
{
return sequence.Where(predicate).One();
}
//usage
if (someIntArray.One(item => item == 3)) ...
The problem with Single() is that it throws an exception if there isn't exactly one element. You can wrap it in a try-catch, but these are cleaner, and more efficient than Count() in most cases where there's more than one matching element. Unfortunately, there's no way around having to check the entire array to verify that there are either no elements or only one that matches a predicate, but this will at least "fail fast" if there are two or more, where Count() will always evaluate the entire Enumerable whether there's one matching element or fifty.
I think you're overthinking this.
var targetNumber = 3;
var hasExactlyOne = someIntArray.Count(i => i == targetNumber) == 1;
Using LINQ expression:
var duplicates = from i in new int[] { 2,3,4,4,5,5 }
group i by i into g
where g.Count() > 1
select g.Key
Results:
{4,5}
And of course you could check duplicates.Count() > 0 or log the ones that are a problem or whatever you need to do.
got it working:
if(someIntArray.Single(item => item = 3) > 0)
doh

Categories