Pass Object properties as parameters into Generic List Extension method - c#

I have a GenericListItem object that has a Text and ID property. I am casting various DB objects into List for the purpose of binding to simple generic list controls.
I would like to write an extension method on List that would allow me to specify the relevant properties of T that would get mapped to the Text and ID properties of GenericListItem and then be able to easily convert any List to a list
The signature for the method would need to then somehow accept the two properties of T so I can then output from the list a new List
is something like this possible?

I'd create an extension method to convert items, and use that to convert the lists:
public static GenericListItem ToListItem<T>(this T obj, Func<T, string> textFunc, Func<T, int> idFunc)
{
return new GenericListItem
{
Text = textFunc(obj),
Id = idFunc(obj)
};
}
public static List<GenericListItem> ToItemList<T>(this IEnumerable<T> seq, Func<T, string> textFunc, Func<T, int> idFunc)
{
return seq.Select(i => i.ToListItem(textFunc, idFunc)).ToList();
}

Instead of reinventing the wheel, you can use Linq:
List<string> list = new List<string>();
list.Add("first");
list.Add("second");
List<ListItem> items = list.Select(s => new ListItem() {Id = s.Length, Text = s.ToLower()}).ToList();
// or if ListItem has a constructor with parameters
List<ListItem> items = list.Select(s => new ListItem(s.Length, s.ToLower()).ToList();
If you really insist on extension, you can wrap the above logic into an extension method, but I don't see the point:
List<ListItem> items = list.ToItemList(s => s.Length, s => s.ToLower());
static class Helper
{
public static List<ListItem> ToItemList<T>(this IList<T> list, Func<T, int> idFunc, Func<T,string> textFunc)
{
return list.Select(s => new ListItem() { Id = idFunc(s), Text = textFunc(s) }).ToList();
}
}
Whatever you choose, don't write a method where you specify properties by name. When you change the name of the properties, compiler will not be able to show you that you have forgotten to update the names in calls to your method, because they are just strings.

public class customList<T> : IList<T>
{
public List<GenericListItem> ToCustomListItem()
{
List<GenericListItem> listItems = new List<GenericListItem>();
//logic to convert list to GenericListItem
}
}
to use do the following
customList<object> list = new customList<object>();
//populate your list
list.ToCustomListItem();
But yes, you can change the names and types as you need to.

Related

C# Sort list by IDs, but without creating a new list

I have 2 lists, one is the original list and the other is a sorted list.
I want to sort the first list by the second list, but my method will not return anything, so it must sort the list in place.
I did this method:
public void SortByList(IEnumerable<JObject> source, IEnumerable<JObject> products)
{
var ids = source.Select(m => m["_id"].ToString()).ToList();
products.OrderBy(m => ids.IndexOf(m["_id"].ToString()));
}
But as you know, OrderBy creates a new list. How can I Sort using the ids?
Well, there are no lists, so i assume that you actually pass lists. If you don't want to create a new list you have to cast it to list or change the parameter type to List<T>. Then you can use List.Sort:
public void SortByList(List<JObject> sourceList, IEnumerable<JObject> products)
{
List<JObject> productList = products as List<JObject> ?? products.ToList();
sourceList.Sort((x1, x2) => productList
.FindIndex(p => p["_id"] == x1["_id"])
.CompareTo(productList.FindIndex(p => p["_id"] == x2["_id"])));
}
Use the yield syntax:
//Returns source sorted by products
//Due to usage of the `this` keyword on the `IEnumerable<JObject> source` parameter,
//this method can be called like a member method on any instance imlpementing `IEnumerable<JObject>`.
public static class MyProductExtensionMethods {
public static IEnumerable<JObject> SortByList(this IEnumerable<JObject> source,
IEnumerable<JObject> products) {
foreach(var prod in products) {
// sorting happens here and assigning the next sorted item
// to the variable `sorted_next` (sorting part obviously not provided)
yield return sorted_next;
}
}
}
more on that topic:
yield
Extension Methods

How to use lambda expressions for a function that takes a list of delegates

I'm building an extension method on IList to be able to output the specified properties of any object passed into it as a list, and output it as a CSV string. It looks like:
public static string OutputCSVString<T>(this IList<T> list, List<Func<T, string>> properties)
{
foreach (var row in list)
{
foreach(var item in properties)
{
// Do the output work, including calling item(row).
}
// Output new line
}
}
Right now, I have to call this method like:
// Assuming I've populated List <Product> ProductList up above...
var columns = new List<Func<Product, string>>();
columns.Add(x => x.Id);
columns.Add(x => x.Name);
string s = ProductList.OutputCSVString(columns);
Is there a better way to pass in my lambda expressions without having to explicitly declare the columns variable, something like:
// This doesn't compile
string s = Products.OutputCSVString(new { p => p.Id , p => p.Name });
Rather than using a List<Func<T, string>> use a Func<T, string>[] and make it a parameter array:
static string OutputCSVString<T>(this IList<T> list,
params Func<T, string>[] properties)
Then you should be able to call:
string s = Products.OutputCSVString(p => p.Id , p => p.Name);
Note that as of C# 6, you should be able to write:
static string OutputCSVString<T>(this IList<T> list,
params IEnumerable<Func<T, string>> properties)
... which would mean you could still use it with a List<Func<T, string>> as well.
Try passing in as a params array
public static string OutputSVString<T>(this IList<T> list, params Func<T, string>[] properties)
{
...
}
This will let you invoke it as
var s = Products.OutputCSVString(p => p.Id, p => p.Name);
Also, as a suggestion, loosen up the function return to an object and then call ToString() when assembling the parts. This way you can pass in any property to include in the CSV list, not just strings.
You could pass your lambdas as an array and use the params keyword:
public static string OutputCSVString<T>(this IList<T> list, params Func<T, string>[] properties)
Usage:
string s = Products.OutputCSVString(p => p.Id, p => p.Name);

Creating a generic select list generating method of type T

I was wondering how I might be able to create a re-usable method that creates select lists based on method arguments? I was thinking something like below:
public IEnumerable<SelectListItem> CreateSelectList(IList<T> entities, T value, T text)
{
return entities
.Select(x => new SelectListItem
{
Value = x.value.ToString(),
Text = x.text.ToString()
});
}
I think I have it a bit backwards though. I'm unsure how to call a method like this, when I call it with an IList of Category for the first argument the compiler complains that it cannot assign type Category to type T? Also, how would I insert the method arguments into the lambda? Any help appreciated!
Code I'm trying to use to call it (which is wrong, but you get the idea)
viewModel.Categories = _formServices.CreateSelectList(categories, Id, Name);
Code I'm trying to make more generic and reusable:
viewModel.Categories = categories
.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
Edit For Answer
Credit goes to #Pavel Backshy for working answer. I wanted to edit in an extension I made to his answer in case it helps anybody! The extension just adds a .Where clause into the mix:
public IEnumerable<SelectListItem> CreateSelectListWhere<T>(IList<T> entities, Func<T, bool> whereClause, Func<T, object> funcToGetValue, Func<T, object> funcToGetText)
{
return entities
.Where(whereClause)
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString()
});
}
You can define this using Reflection to take property value by name, but I think more elegant and flexible to use Func.
Change your method to:
public IEnumerable<SelectListItem> CreateSelectList<T>(IList<T> entities, Func<T, object> funcToGetValue, Func<T, object> funcToGetText)
{
return entities
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString()
});
}
And then you can use it by this way:
viewModel.Categories = _formServices.CreateSelectList(categories, x => x.Id, x => x.Name);
I found Pavel Bakshy's answer, as well as your edit to include the 'whereClause' very helpful, and exactly what I was trying to accomplish.
Along the same lines I also added a 'selectedValue' object as this was part of what I was trying to do. The 'null' check is for when a list does not have currently selected value (i.e. when it's first loaded).
Edit: Also I used an IEnumerable instead of IList as my first parameter
IEnumerable<SelectListItem> ISelectUtils.CreateSelectList<T>(IEnumerable<T> entities, Func<T, bool> whereClause, Func<T, object> funcToGetValue, Func<T, object> funcToGetText, object selectedValue)
{
return entities
.Where(whereClause)
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString(),
Selected = selectedValue != null ? ((funcToGetValue(x).ToString() == selectedValue.ToString()) ? true : false) : false
});
}

C# Pattern to cache actions accepting generic parameters

I want to create a list of actions to perform on a drawing object. Here's what the non-generic code would look like:
private Dictionary<String, Action<Drawing, String>> actions = new Dictionary<String, Action<Drawing, String>>();
private void LoadActions()
{
actions.Add("Height", (d, s) => d.Height = Double.Parse(s));
actions.Add("Width", (d, s) => d.Width = Double.Parse(s));
}
private void ProcessDrawing(Drawing drawing, String prop, String value)
{
actions[prop](drawing, value);
}
The problem I have is that the Drawing class is a generic one (Drawing<T>) so I can't define actions like the following because T is not defined:
Dictionary<String, Action<Drawing<T>, String>> actions = new Dictionary<String, Action<Drawing<T>, String>>();
Without caching the code looks like this:
private void ProcessDrawing<T>(Drawing<T> drawing, String prop, String value)
{
var actions = new Dictionary<String, Action<Drawing<T>, String>>();
actions.Add("Height", (d, s) => d.Height = Double.Parse(s));
actions.Add("Width", (d, s) => d.Width = Double.Parse(s));
actions[prop](drawing, value);
}
So how can I cache a bunch of actions accepting a generic type of parameter?
Thanks,
The base class of all Actions is MulticastDelegate. You would have to define your dict as Dictionary<String,MulticastDelegate> and use appropriate castings after retrieving your actions from the dict.
EDIT:
Tests show that lambda expressions can obviously not be directly assigned to variables of type MulticastDelegate. This is because the type of the lambda expression parameters is inferred from the type of the variable (or method parameter) it is assigned to. Therefore assign it first to a variable with the right Action<> type. Then assign this to MulticastDelegate.
In the example, I show both versions (through a method parameter and through a variable):
public static void CallTestDelegate()
{
TestDelegate((d, s) => d.Height = Single.Parse(s));
}
public static void TestDelegate(Action<RectangleF, string> action)
{
Dictionary<String, MulticastDelegate> dict = new Dictionary<string, MulticastDelegate>();
dict.Add("a1", action);
Action<RectangleF, string> action2 = (d, s) => d.Width = Single.Parse(s);
dict.Add("a2", action2);
var a1 = (Action<RectangleF, string>)dict["a1"];
a1(new RectangleF(), "15");
}
One option is to make Drawing<T> derive from a non-generic interface IDrawing that has Height and Width properties, then change your actions to be of type Action<IDrawing,String>.
Another option (less type-safe) is to make your actions of type Action<dynamic,String>.
T is just a placeholder, make an abstract class or interface representing objects that you want

Generate a generic list of some given type

My friend is trying to create a utility function that is given some Type and in that function it creates a generic List of that type. We're having trouble creating that list:
public static List<T> GetQueryResult(string xpathQuery, Type itemType) {
// this line does not work:
List<itemType> lst = new List<itemType>();
return lst;
}
Are there any easy solutions to this?
UPDATE:
Is there any way to basically do this???
List<T> lst = new List<T>();
foreach (Sitecore.Data.Items.Item i in items) {
lst.Add(new T(i));
}
public static List<T> GetQueryResult<T>(string xpathQuery/*, Type itemType you don't need this because you specified the Type with T*/) {
// this line works now:
List<T> lst = new List<T>();
return lst;
}
Then you would call the method like so:
List<int> results = GetQueryResult<int>("xpathQuery");
Edit:
Are you wanting to do something like this?
List<YourType> lst = items.Select<Sitecore.Data.Items.Item, YourType>(
siteCoreItem => new YourType()
{
PropertyA = siteCoreItem.PropertyA,
}
);
If YourType inherrits from Sitecore.Data.Items.Item you can use Cast:
List<YourType> list = items.Cast<YourType>();
Define that method like this:
public static List<T> GetQueryResult<T>(string xpathQuery)
{
List<T> lst = new List<T>();
// do stuff
return lst;
}
and call it like this:
List<SomeType> items = SomeClass.GetQueryResult<SomeType>("query");
It is possible using reflection, for example:
var type = typeof(int); // var type = itemType : put this line to fit the method
var genericListType = typeof(List<>).MakeGenericType(type);
var genericList = Activator.CreateInstance(genericListType);
Assert.IsTrue(genericList is List<int>);
In your example, ehere do you get T from that you use in the return type? Maybe there is no need to use here reflection.
If you do not get T as generic argument then you cannot return the List as generic List and the method will have to return a non generic type (like IList instead of List).
While Elisha's answer shows you how you can create a constructed generic type from a Type instance, it's not going to help you because what I think you want to do is not possible: the signature of the GetQueryResult method is illegal because T is unspecified (unless the method is a member of a generic type itself).
The method will not compile as given.
If you already know the type, you can change it to
public static List<T> GetQueryResult<T>(string xpathQuery)
{
var lst = new List<T>();
return lst;
}
but that's probably not what you want...
Generic type arguments are resolved compile time, so to have the code working you'd need to pass itemType as a type argument or change the return type to IList And Them use the solution given by ELisha but that would mean loosing type information on the Call site
Answer to the updated question:
public List<T> GetQueryResult<T>(string xPathQuery)
{
var items = ;// logic to get items
var list = new List<T>();
foreach (Sitecore.Data.Items.Item item in items)
{
list.Add((T) Activator.CreateInstance(typeof(T), item));
}
return list;
}
I assume that T has a constructor that gets Sitecore.Data.Items.Item, if it won't have the code will fail at runtime.
There must be a safer way to do it, it'll be better if you can give wider context to the problem.
As others have demonstrated, the only way to solve your updated question for any T is with reflection. However, if T is restricted to a well known set of types that you can modify, you could do this:
public interface IItemContainer
{
void SetItem(Sitecore.Data.Items.Item item);
}
public static List<T> GetQueryResult<T>(string xpathQuery)
where T : IItemContainer, new() {
IList<Sitecore.Data.Items.Item> items = GetAListOfItemsSomehow(xpathQuery);
List<T> result = new List<T>();
foreach (Sitecore.Data.Items.Item item in items) {
T obj = new T();
obj.SetItem(item);
result.add(obj);
}
return result;
}
Any types you want to use for T would then have to implement IItemContainer.
public static List<T> GetQueryResult<T>(string xpathQuery) {
List<T> lst = new List<T>();
return lst;
}
is the only way if you want static typing. Otherwise you could do
public static IList GetQueryResults(string xpathQuery, Type itemType) {
Type tp = typeof(List<>).MakeGenericType(itemType);
IList lst = (IList)Activator.CreateInstance(tp);
return lst;
}
but using a non-generic list would probably be better in that case.
Edit: You asked another question in the same post:
The 3 ways of creating an instance of a generic type are
use the where T : new() constraint and use the default constructor (doesn't seem good enough for you).
Use reflection. Rarely the best idea.
Specify a creator function
like this:
public static List<T> GetQueryResults<T>(string xpathQuery, Func<int, T> creator) {
var result = new List<T>();
foreach (i in something)
result.add(creator(i));
return result;
}
and then invoke it like:
List<int> l = GetQueryResults("something", i => new MyObject(i));

Categories