I would like to pass an expression that represents a variable to used when instantiating an object.
Instead of:
class MyObject : IMyInterface { ... }
var list = db.MyObjects.Where(x => !x.IsDeleted).ToList();
var anotherList = list.Select(x => new AnotherObject() {
Id = x.Id,
Value = x.Value
});
I would like to make this so that a list of objects of IMyInterface can be transformed into another type of list (AnotherObject as example) using defined expressions as so:
var list = db.MyObjects
.Where(x => !x.IsDeleted)
.ToAnotherObjectList(x => x.Id, x => x.Value);
...
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<IMyInterface> list,
Expression id,
Expression value)
{
return list.Select(x => new AnotherObject() { Id = id, Value = value }).ToList();
}
I'm not sure how to accomplish this. I know I can use reflection to create objects and set properties by a string but I'm not sure how to pass expressions.
UPDATE
Well, I thought I'd have to do some reflection but it's simpler than what I was thinking. Here's my solution that works in IRL.
public static IEnumerable<AnotherObject> ToAnotherObject<T>(this IEnumerable<T> list, Func<T, int> getId, Func<T, string> getValue, Func<T, bool> getSelected = null) where T : IMyInterface
{
return list.Select(x => new AnotherObject {
Display = getValue(x),
Id = getId(x),
Selected = getSelected != null && getSelected(x),
});
}
You could use a Func<TInput,TReturn> for that. For example:
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<T> list,
Func<T, int> getId,
Func<T, object> getValue)
{
return list.Select(x => new AnotherObject() { Id = getId(x), Value = getValue(x) }).ToList();
}
Call:
list.ToAnotherObjectList(i => i.Id, i=> i.Value);
In this example I used Funcs with one parameter (of type T) and return type int/object.
I have a list of Person objects. I want to convert to a Dictionary where the key is the first and last name (concatenated) and the value is the Person object.
The issue is that I have some duplicated people, so this blows up if I use this code:
private Dictionary<string, Person> _people = new Dictionary<string, Person>();
_people = personList.ToDictionary(
e => e.FirstandLastName,
StringComparer.OrdinalIgnoreCase);
I know it sounds weird but I don't really care about duplicates names for now. If there are multiple names I just want to grab one. Is there anyway I can write this code above so it just takes one of the names and doesn't blow up on duplicates?
LINQ solution:
// Use the first value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
// Use the last value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);
If you prefer a non-LINQ solution then you could do something like this:
// Use the first value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
if (!_people.ContainsKey(p.FirstandLastName))
_people[p.FirstandLastName] = p;
}
// Use the last value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
_people[p.FirstandLastName] = p;
}
Here's the obvious, non linq solution:
foreach(var person in personList)
{
if(!myDictionary.ContainsKey(person.FirstAndLastName))
myDictionary.Add(person.FirstAndLastName, person);
}
If you don't mind always getting the last one added, you can avoid the double lookup like this:
foreach(var person in personList)
{
myDictionary[person.FirstAndLastName] = person;
}
A Linq-solution using Distinct() and and no grouping is:
var _people = personList
.Select(item => new { Key = item.Key, FirstAndLastName = item.FirstAndLastName })
.Distinct()
.ToDictionary(item => item.Key, item => item.FirstFirstAndLastName, StringComparer.OrdinalIgnoreCase);
I don't know if it is nicer than LukeH's solution but it works as well.
This should work with lambda expression:
personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
You can create an extension method similar to ToDictionary() with the difference being that it allows duplicates. Something like:
public static Dictionary<TKey, TElement> SafeToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer = null)
{
var dictionary = new Dictionary<TKey, TElement>(comparer);
if (source == null)
{
return dictionary;
}
foreach (TSource element in source)
{
dictionary[keySelector(element)] = elementSelector(element);
}
return dictionary;
}
In this case, if there are duplicates, then the last value wins.
You can also use the ToLookup LINQ function, which you then can use almost interchangeably with a Dictionary.
_people = personList
.ToLookup(e => e.FirstandLastName, StringComparer.OrdinalIgnoreCase);
_people.ToDictionary(kl => kl.Key, kl => kl.First()); // Potentially unnecessary
This will essentially do the GroupBy in LukeH's answer, but will give the hashing that a Dictionary provides. So, you probably don't need to convert it to a Dictionary, but just use the LINQ First function whenever you need to access the value for the key.
To handle eliminating duplicates, implement an IEqualityComparer<Person> that can be used in the Distinct() method, and then getting your dictionary will be easy.
Given:
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.FirstAndLastName.Equals(y.FirstAndLastName, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Person obj)
{
return obj.FirstAndLastName.ToUpper().GetHashCode();
}
}
class Person
{
public string FirstAndLastName { get; set; }
}
Get your dictionary:
List<Person> people = new List<Person>()
{
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Jane Thomas" }
};
Dictionary<string, Person> dictionary =
people.Distinct(new PersonComparer()).ToDictionary(p => p.FirstAndLastName, p => p);
In case we want all the Person (instead of only one Person) in the returning dictionary, we could:
var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));
The issue with most of the other answers is that they use Distinct, GroupBy or ToLookup, which creates an extra Dictionary under the hood. Equally ToUpper creates extra string.
This is what I did, which is an almost an exact copy of Microsoft's code except for one change:
public static Dictionary<TKey, TSource> ToDictionaryIgnoreDup<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null) =>
source.ToDictionaryIgnoreDup(keySelector, i => i, comparer);
public static Dictionary<TKey, TElement> ToDictionaryIgnoreDup<TSource, TKey, TElement>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
{
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (elementSelector == null)
throw new ArgumentNullException(nameof(elementSelector));
var d = new Dictionary<TKey, TElement>(comparer ?? EqualityComparer<TKey>.Default);
foreach (var element in source)
d[keySelector(element)] = elementSelector(element);
return d;
}
Because a set on the indexer causes it to add the key, it will not throw, and will also do only one key lookup. You can also give it an IEqualityComparer, for example StringComparer.OrdinalIgnoreCase
DataTable DT = new DataTable();
DT.Columns.Add("first", typeof(string));
DT.Columns.Add("second", typeof(string));
DT.Rows.Add("ss", "test1");
DT.Rows.Add("sss", "test2");
DT.Rows.Add("sys", "test3");
DT.Rows.Add("ss", "test4");
DT.Rows.Add("ss", "test5");
DT.Rows.Add("sts", "test6");
var dr = DT.AsEnumerable().GroupBy(S => S.Field<string>("first")).Select(S => S.First()).
Select(S => new KeyValuePair<string, string>(S.Field<string>("first"), S.Field<string>("second"))).
ToDictionary(S => S.Key, T => T.Value);
foreach (var item in dr)
{
Console.WriteLine(item.Key + "-" + item.Value);
}
Using LINQ's equivalent of foldLeft functionality
persons.Aggregate(new Dictionary<string,Person>(StringComparer.OrdinalIgnoreCase),
(acc, current) => {
acc[current.FirstAndLastName] = current;
return acc;
});
Starting from Carra's solution you can also write it as:
foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
myDictionary.Add(person.FirstAndLastName, person);
}
I want to put Func where T is a generic type as values in a dictionary. So Basically I want to do something like:
Dictionary<string, Func<MyObject, T>> _sortMappings =
new Dictionary<string, Func<MyObject, T>>()
{
{ "Name", (b) => b.Name }, // name is a string
{ "Length", (b) => b.Length }, // length is an int
{ "Date", (b) => b.Date } // a datetime object
};
Does this make sense and is this possible?
It is not possible with Func<MyObject, T> you need to use Func<MyObject, object>. T can't be more than one type at the same time. So you need to find a common type for string, int and DateTime which is object.
try in this way using Convert.ChangeType Method
"Name", b => (T)System.Convert.ChangeType(b.Name,typeof(T))
your function should be
private static T InvokeFunction<T>(Func<T> func)
{
Dictionary<string, Func<MyObject, T>> sortMappings =
new Dictionary<string, Func<MyObject, T>>();
sortMappings.Add("Name", b => (T)System.Convert.ChangeType(b.Name,typeof(T)));
sortMappings.Add("Length", b => (T)System.Convert.ChangeType(b.Length, typeof(T)));
sortMappings.Add("Date", b => (T)System.Convert.ChangeType(b.Date, typeof(T)));
return func.Invoke();
}
Following is my code to convert enum values to Dictionary.
public static Dictionary<string, string> EnumToDictionary<T>() where T : struct, IConvertible
{
var oResult = new Dictionary<string, string>();
if (typeof(T).IsEnum)
foreach (T oItem in Enum.GetValues(typeof(T)))
oResult.Add(oItem.ToString(), oItem.ToString());
return oResult;
}
and this is my enum
public enum MyEnum
{
Value1,
Value2,
value3
}
Currently I am calling that method like
var result=EnumToDictionary<MyEnum>();
but I need to use that method like
var result=MyEnum.EnumToDictionary();
or any other way like string extension methods.
In general your problem is connected with the fact that you want to create a generic extensions method (that's possible) but without any object reference sent as "this" parameter when calling such a method (that's not possible).
So using extension methods is not an option to achieve what you want.
You could do sth like this:
public static Dictionary<string, string> EnumToDictionary(this Enum #enum)
{
var type = #enum.GetType();
return Enum.GetValues(type).Cast<string>().ToDictionary(e => e, e => Enum.GetName(type, e));
}
But this would mean that you need to operate on a certain instance of enum class to call such an extension method.
Or you could do this in such a way:
public static IDictionary<string, string> EnumToDictionary(this Type t)
{
if (t == null) throw new NullReferenceException();
if (!t.IsEnum) throw new InvalidCastException("object is not an Enumeration");
string[] names = Enum.GetNames(t);
Array values = Enum.GetValues(t);
return (from i in Enumerable.Range(0, names.Length)
select new { Key = names[i], Value = (int)values.GetValue(i) })
.ToDictionary(k => k.Key, k => k.Value.ToString());
}
And then call it like this:
var result = typeof(MyEnum).EnumToDictionary();
You could write an extension method, something like:
public static IDictionary<string, string> ToDictionary(this Enum value)
{
var result = new Dictionary<string, string>();
foreach (var item in Enum.GetValues(value.GetType()))
result.Add(Convert.ToInt64(item).ToString(), item.ToString());
return result;
}
But to call such an extension method, you need to provide an instance of the required enum. E.g.
var dict = default(System.DayOfWeek).ToDictionary();
I dont know how to get rid of this error ?
Error 1 Using the generic type 'System.Collections.Generic.IEnumerable' requires 1 type arguments C:\Users\huzaifa.gain\documents\visual studio 2010\Projects\VendInvoiceImport\VendInvoiceImport\Program.cs 34 24 VendInvoiceImport
private static IEnumerable<string , string > DistinctInvoiceNumber(DataTable VendorInvoiceStagingTable)
{
var InvoiceLinecollection = VendorInvoiceStagingTable.AsEnumerable().Select(t => new { number = t.Field<string>(VendInvoice.Number),LineNumber = t.Field<string>(VendInvoice.LineNumber)}).Distinct();
return InvoiceLinecollection;
}
Your Linq query returns an sequence of anonymous type, but methods can't return anonymous types. You have several options:
return an IEnumerable<Tuple<string, string>>
private static IEnumerable<Tuple<string, string>> DistinctInvoiceNumber(DataTable VendorInvoiceStagingTable)
{
var InvoiceLinecollection = VendorInvoiceStagingTable
.AsEnumerable()
.Select(t => Tuple.Create(t.Field<string>(VendInvoice.Number), t.Field<string>(VendInvoice.LineNumber)))
.Distinct();
return InvoiceLinecollection;
}
return a IDictionary<string, string> as suggested in another answer (assuming your query doesn't return duplicate keys)
private static IDictionary<string, string> DistinctInvoiceNumber(DataTable VendorInvoiceStagingTable)
{
var InvoiceLinecollection = VendorInvoiceStagingTable
.AsEnumerable()
.Select(t => Tuple.Create(t.Field<string>(VendInvoice.Number), t.Field<string>(VendInvoice.LineNumber)))
.Distinct()
.ToDictionary(t => t.Item1, t => t.Item2);
return InvoiceLinecollection;
}
create a class for this purpose with 2 string properties and return a sequence of that class
private static IEnumerable<InvoiceLine> DistinctInvoiceNumber(DataTable VendorInvoiceStagingTable)
{
var InvoiceLinecollection = VendorInvoiceStagingTable
.AsEnumerable()
.Select(t => new InvoiceLine(t.Field<string>(VendInvoice.Number), t.Field<string>(VendInvoice.LineNumber)))
.Distinct();
return InvoiceLinecollection;
}
Why you do not use Dictionary<string, string> instead?
private static Dictionary<string , string > DistinctInvoiceNumber(DataTable VendorInvoiceStagingTable)
{
var InvoiceLinecollection = VendorInvoiceStagingTable.AsEnumerable().Select(t => new { number = t.Field<string>(VendInvoice.Number),LineNumber =
t.Field<string>(VendInvoice.LineNumber)}).ToDictionary();
return InvoiceLinecollection;
}