I have a class with a lot of different string values
I have a collection class holding these with some sorting functions. Currently i have lots of reproduced code for "GetListOfDifferentProducts" "GetListOfDifferentManufacturers" etc.
Is this possible? Or am i going about this the wrong way.
These are all simple foreach loops
public List<string> GetListOfDifferentProducts()
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.Name.ToLower()) == false)
listOfResults.Add(prod.Name.ToLower());
}
return listOfResults;
}
I'd like to pass in a class variable (Like LINQ?)
public List<string> GetListOfDifferentVariables(variableType)
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.variableType.ToLower()) == false)
listOfResults.Add(prod.variableType.ToLower());
}
return listOfResults;
}
example Usage:
ProductList.GetListOfDifferentVariables(o => o.Name);
Sample input (Variable string Name)
Apple
Apple
Apple
Pear
Banana
Banana
Output
apple
pear
banana
mvc
class Product
{
public string Name;
public string Manufacturer;
public string Description;
public string Location;
}
class ProductCollection
{
List<Product> listOfProducts;
public List<string> GetListOfDifferentProducts()
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.Name.ToLower()) == false)
listOfResults.Add(prod.Name.ToLower());
}
return listOfResults;
}
}
If you are looking for the distinct set of names, consider:
listOfProducts.Select(z => z.Name.ToLower()).Distinct();
Similarly for variableType:
listOfProducts.Select(z => z.variableType.ToLower()).Distinct();
This will avoid you needing to write explicit GetListOfDifferentVariables etc methods.
If you really want those methods, try something like:
public List<string> GetDistinctNames()
{
return GetDistinctProperties(product => product.Name.ToLower());
}
public List<string> GetDistinctProperties(Func<Product, string> evaluator)
{
return listOfProducts.Select(evaluator).Distinct().ToList();
}
whereby GetDistinctNames passes in the particular field (i.e. Name) and manipulation (i.e. ToLower) it is interested in.
string[] tmp =
{
"Apple",
"Apple",
"Apple",
"Pear",
"Banana",
"Banana"
};
string[] tmp2 =
{
"Pear",
"Banana"
};
IEnumerable<string> uniqueList = tmp.Distinct(); // Removes doubles
IEnumerable<string> notInTmp2 = tmp.Distinct().Except(tmp2); // could be also interesting ;)
Console.WriteLine(string.Join(", ", uniqueList));
Console.WriteLine(string.Join(", ", notInTmp2));
If you want to pass the Property name as a parameter then you can use Reflection.
You will need to slightly tweak your Product class to have public properties.
public class Product
{
private string _name, _manufacturer;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public string Manufacturer
{
get
{
return _manufacturer;
}
set
{
_manufacturer = value;
}
}
}
Then you can write your GetListOfDifferentVariables function as
public List<string> GetListOfDifferentVariables(string propertyName)
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
string propertyValue = (string)prod.GetType().GetProperty(propertyName).GetValue(prod);
if (listOfResults.Contains(propertyValue.ToLower()) == false)
listOfResults.Add(propertyValue.ToLower());
}
return listOfResults.Distinct().ToList();
}
you can call the function using the name of the property
var names = GetListOfDifferentVariables("Name");
var manufacturers = GetListOfDifferentVariables("Manufacturer");
Create general method:
public IEnumerable<T2> GetListOfDifferent<T1, T2>(Func<T1,T2> selector, IEnumerable<T1> inputs)
{
var result = inputs.Select(selector).Distinct();
return result;
}
public IEnumerable<T2> GetListOfDifferent<T1, T2>(string propertyName, IEnumerable<T1> inputs)
{
var paramExp = Expression.Parameter(typeof(T1), "p1");
var member = Expression.PropertyOrField(paramExp, propertyName);
var lamdaExp = Expression.Lambda<Func<T1, T2>>(member, new[] { paramExp });
var selector = lamdaExp.Compile();
var result = GetListOfDifferent(selector, inputs);
return result;
}
Usage:
var result =GetListOfDifferent<Product,string>(product => product.Name, items)
or
var result = GetListOfDifferent<Product,string>(nameof(Product.Name), items);
Related
I have a Generic List of type List<InstanceDataLog> which has huge number of properties in it. I want to pass names of a few properties to a method and want to extract a refined List from within this list.
public void Export(string col) //a,b,c
{
string [] selectedcol = col.Split(',');
var grid = new GridView();
var data = TempData["InstanceDataList"];
List<InstanceDataLog> lst = new List<InstanceDataLog>();
List<EToolsViewer.APIModels.InstanceDataLog> lstrefined = new List<InstanceDataLog>();
lst= (List<EToolsViewer.APIModels.InstanceDataLog>)TempData["InstanceDataList"];
var r= lst.Select(e => new {e.a, e.b}).ToList();// I would like to replace these hardcoded properties with names of properties present in `selectedcol `
grid.DataSource =r;
grid.DataBind();
}
To clear things up further, suppose InstanceDataLog has 5 properties : a,b,c,d,e I would like pass a,b and be able to extract a new list with only properties a,b
EDIT:
$('#export').mousedown(function () {
window.location = '#Url.Action("Export", "TrendsData",new { col = "a,b,c" })';
});
You could use such method to get properties:
private object getProperty(EToolsViewer.APIModels.InstanceDataLog e, string propName)
{
var propInfo =typeof(EToolsViewer.APIModels.InstanceDataLog).GetProperty(propName);
return propInfo.GetValue(e);
}
and with another function you could get all properties you want:
private dynamic getProperties(string[] props, EToolsViewer.APIModels.InstanceDataLog e )
{
var ret = new ExpandoObject() as IDictionary<string, Object>;;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
The problem occurs if you try to assign DataSource with expando object. Solution is described hier:
Binding a GridView to a Dynamic or ExpandoObject object
We do need one more method:
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
var data = items.ToArray();
if (data.Count() == 0) return null;
var dt = new DataTable();
foreach (var key in ((IDictionary<string, object>)data[0]).Keys)
{
dt.Columns.Add(key);
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}
and use it:
var r = lst.Select(e => getProperties(selectedcol, e)).ToList();
grid.DataSource = ToDataTable(r);
The same thing, but ready to run for LinqPad:
void Main()
{
var samples = new[] { new Sample { A = "A", B = "B", C = "C" }, new Sample { A = "A1", B = "B2", C = "C1" } };
var r = samples.Select(e => getProperties(new[] {"A", "C", "B"}, e)).ToList();
r.Dump();
}
private object getProperty(Sample e, string propName)
{
var propInfo = typeof(Sample).GetProperty(propName);
return propInfo.GetValue(e);
}
private dynamic getProperties(string[] props, Sample e)
{
var ret = new ExpandoObject() as IDictionary<string, Object>; ;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
public class Sample
{
public string A { get; set;}
public string B { get; set;}
public string C { get; set;}
}
With output:
To keep compiler type/name checking suggest to pass a Func<InstanceDataLog, TResult> instead of array of names
public void Export<TResult>(Func<InstanceDataLog, TResult> selectProperties)
{
var grid = new GridView();
var data = TempData["InstanceDataList"];
var originalList = (List<InstanceDataLog>)TempData["InstanceDataList"];
var filteredList = originalList.Select(selectProperties).ToList();
grid.DataSource = filteredList;
grid.DataBind();
}
Then use it:
Export(data => new { Id = data.Id, Name = data.Name });
Can any body help on this.
here i am using datasource as list for combobox.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
LoadDropdownlist();
}
public void LoadDropdownlist()
{
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
};
comboBox1.DataSource = makes;
}
as per above code i am getting result in combobox as
Audi,
BMW,
Ford,
Vauxhall,
Volkswagen
but i want to display unique records based on first character.So i am expecting below result in combobox.
Audi,
BMW,
Ford,
Vauxhall
thanks,
satish
public void LoadDropdownlist()
{
var makes = new List<string> {"Volkswagen","Audi","BMW","Ford","Vauxhall"};
FirstLetterComparer comp = new FirstLetterComparer();
comboBox1.DataSource= makes.Distinct(comp).ToList();//makes.GroupBy(x=>x[0]).Select(x=>x.First()).ToList();
}
public class FirstLetterComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y))
return false;
//ignoring case
return string.Compare(x[0].ToString(), y[0].ToString(), 0) == 0;
}
public int GetHashCode(string str)
{
return 1;
}
}
When checking two objects for equality the following happens:
First, GetHashCode is called on both objects. If the hash code is different, the objects are considered not equal and Equals is never called.
Equals is only called when GetHashCode returned the same value for both objects.
Solution for .Net 2
In .Net 2 you need to loop through items as this
public List<string> FetchFirstLetterUniques(List<string> source)
{
List<string> result = new List<string>();
foreach (string item in source)
{
bool isAdded = false;
foreach (string item2 in result)
{
if (string.Compare(item2[0].ToString(), item[0].ToString(), 0) == 0)
{
isAdded = true;
break;
}
}
if (!isAdded)
result.Add(item);
}
return result;
}
public void LoadDropdownlist()
{
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
};
comboBox1.DataSource = FetchFirstLetterUniques(makes);
}
You can group them based on first characters:
comboBox1.DataSource = makes
.GroupBy(x => x[0])
.Select(x => x.First())
.ToList();
try this
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
}.GroupBy(t => t[0])
.Select(t => t.First()).ToList();
I am using VS2010 and EF4.0. The goal is to select fields of any IEnumerable, in order to show in the DataGridView. Take Northwind.Employees as example, the following code is OK.
private void button1_Click(object sender, EventArgs e)
{
NorthwindEntities en = new NorthwindEntities();
dataGridView1.DataSource = SelectNew(en.Employees, new string[] { "EmployeeID", "FirstName" });
}
public object SelectNew(object items, string[] fields)
{
IEnumerable<Employee> ems = items as IEnumerable<Employee>;
return ems.Select(em => new
{
id = em.EmployeeID,
name = em.FirstName
}
).ToArray();
}
The parameter object items is IEnumerable of EntityObject, and the function will be executed at client side memorry and shall have nothing to do with database now.
But I don't know the EntityObject type (Employee) until runtime, so maybe some complex reflection will be used.
I have checked this,
but when I bind the result to the control, it showed only blank rows without any column or data. And the funciton is for IQueryable, I have tried IEnumerable.AsQueryable and pass to it, but the results did not show any column either.
I've modified the example I pointed to in my comment above. This actually returns an IEnumerable<Dictionary<string,object>>, where each Dictionary represents one of the "new objects", and each key value pair in the dictionary represents a property and its value. Perhaps you can modify this for your use?
I'm not sure if you can simply bind the result to the DataGrid, but you should be able to figure it out.
I don't believe it's possible to create an anonymous type on the fly... But it might be possible to change this to use a dynamic type like ExpandoObject instead of a Dictionary. See this question for some hints on how to do that. I've never used dynamic objects, so you're on your own there!
public class TestClassA {
public string SomeString { get; set; }
public int SomeInt { get; set; }
public TestClassB ClassB { get; set; }
}
public class TestClassB {
public string AnotherString { get; set; }
}
public class Program {
private static void Main(string[] args) {
var items = new List<TestClassA>();
for (int i = 0; i < 9; i++) {
items.Add(new TestClassA {
SomeString = string.Format("This is outer string {0}", i),
SomeInt = i,
ClassB = new TestClassB { AnotherString = string.Format("This is inner string {0}", i) }
});
}
var newEnumerable = SelectNew(items, new string[] { "ClassB.AnotherString" });
foreach (var dict in newEnumerable) {
foreach (var key in dict.Keys)
Console.WriteLine("{0}: {1}", key, dict[key]);
}
Console.ReadLine();
}
public static IEnumerable<Dictionary<string, object>> SelectNew<T>(IEnumerable<T> items, string[] fields) {
var newItems = new List<Dictionary<string, object>>();
foreach (var item in items) {
var dict = new Dictionary<string, object>();
foreach (var field in fields)
dict[field] = GetPropertyValue(field, item);
newItems.Add(dict);
}
return newItems;
}
private static object GetPropertyValue(string property, object o) {
if (property == null)
throw new ArgumentNullException("property");
if (o == null)
throw new ArgumentNullException("o");
Type type = o.GetType();
string[] propPath = property.Split('.');
var propInfo = type.GetProperty(propPath[0]);
if (propInfo == null)
throw new Exception(String.Format("Could not find property '{0}' on type {1}.", propPath[0], type.FullName));
object value = propInfo.GetValue(o, null);
if (propPath.Length > 1)
return GetPropertyValue(string.Join(".", propPath, 1, propPath.Length - 1), value);
else
return value;
}
}
I am trying to create a generic method that takes three parameters.
1) List collection
2) String PropertyName
3) String FilterString
The idea is we pass a collection of Objects, the name of the property of the object
and a Filter Criteria and it returns back a list of Objects where the property contains
the FilterString.
Also, the PropertyName is optional so if its not supplied I would like to return
all objects that contain the FilterString in any property.
Any pointers on this would be really helpful.
I am trying to have a method signature like this:
public static List FilterList(List collection, String FilterString, String Property = "")
This way I can call this method from anywhere and pass it any List and it would return me a filtered list.
You could do what you want using LINQ, as such,
var collection = ...
var filteredCollection =
collection.Where(item => item.Property == "something").ToList();
Otherwise, you could try Reflection,
public List<T> Filter<T>(
List<T> collection,
string property,
string filterValue)
{
var filteredCollection = new List<T>();
foreach (var item in collection)
{
// To check multiple properties use,
// item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
var propertyInfo =
item.GetType()
.GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
throw new NotSupportedException("property given does not exists");
var propertyValue = propertyInfo.GetValue(item, null);
if (propertyValue == filterValue)
filteredCollection.Add(item);
}
return filteredCollection;
}
The problem with this solution is that changes to the name of the property or misspellings result in a runtime error, rather than a compilation error as would be using an actual property expression where the name is hard-typed.
Also, do note that based on the binding flags, this will work only on public, non-static properties. You can modify such behavior by passing different flags.
You can use a combination of reflection and dynamic expressions for this. I've put together a sample that might look a bit long at the first look. However, it matches your requirements and addresses these by
Using reflection to find the properties that are of type string and match the property name - if provided.
Creating an expression that calls string.Contains on all properties that have been identified. If several properties have been identified, the calls to string.Contains are combined by Or-expressions. This filter expression is compiled and handed to the Where extension method as a parameter. The provided list is filtered using the expression.
Follow this link to run the sample.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
public class Test
{
public static IEnumerable<T> SelectItems<T>(IEnumerable<T> items, string propName, string value)
{
IEnumerable<PropertyInfo> props;
if (!string.IsNullOrEmpty(propName))
props = new PropertyInfo[] { typeof(T).GetProperty(propName) };
else
props = typeof(T).GetProperties();
props = props.Where(x => x != null && x.PropertyType == typeof(string));
Expression lastExpr = null;
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x");
ConstantExpression valueExpr = Expression.Constant(value);
foreach(var prop in props)
{
var propExpr = GetPropertyExpression(prop, paramExpr, valueExpr);
if (lastExpr == null)
lastExpr = propExpr;
else
lastExpr = Expression.MakeBinary(ExpressionType.Or, lastExpr, propExpr);
}
if (lastExpr == null)
return new T[] {};
var filterExpr = Expression.Lambda(lastExpr, paramExpr);
return items.Where<T>((Func<T, bool>) filterExpr.Compile());
}
private static Expression GetPropertyExpression(PropertyInfo prop, ParameterExpression paramExpr, ConstantExpression valueExpr)
{
var memberAcc = Expression.MakeMemberAccess(paramExpr, prop);
var containsMember = typeof(string).GetMethod("Contains");
return Expression.Call(memberAcc, containsMember, valueExpr);
}
class TestClass
{
public string SomeProp { get; set; }
public string SomeOtherProp { get; set; }
}
public static void Main()
{
var data = new TestClass[] {
new TestClass() { SomeProp = "AAA", SomeOtherProp = "BBB" },
new TestClass() { SomeProp = "BBB", SomeOtherProp = "CCC" },
new TestClass() { SomeProp = "CCC", SomeOtherProp = "AAA" },
};
var result = SelectItems(data, "", "A");
foreach(var item in result)
Console.WriteLine(item.SomeProp);
}
}
In contrast to a completely reflection based approach, this one assembles the filter expression only once and compiles it, so that I'd expect a (small) performance improvement.
You should use Dynamic LINQ, for example, given SomeClass:
public class SomeClass
{
public int SomeField { get; set; }
}
List<SomeClass> list = new List<SomeClass>() { new SomeClass() { SomeField = 2 } };
and then:
var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
var result= temp .Cast<SomeClass>().ToList();
So your function would be even simpler, with property name and filter merged into one parameter:
public List<T> Filter<T>(List<T> list, string filter)
{
var temp = list.AsQueryable().Where(filter).Select("it");
return temp.Cast<T>().ToList();
}
and you can provide different filters, like for example "SomeField > 4 && SomeField < 10" etc.
When using Markus solution it'll only work when all String properties are not null.
To ensure you could do this:
class TestClass
{
private string _someProp { get; set; }
public string SomeProp {
get
{
if(string.IsNullOrEmpty(_someProp)
{
_someProp = "";
}
return _someProp;
}
set
{
_someProp = value;
}
}
private string _someOtherProp { get; set; }
public string SomeOtherProp {
get
{
if(string.IsNullOrEmpty(_someOtherProp)
{
_someOtherProp = "";
}
return _someOtherProp;
}
set
{
_someOtherProp = value;
}
}
}
Since my rep is less then 50 I cannot comment;)
I am new to Reflection so please excuse my noob question. How can I create a Method that takes two Parameters, a Generic List and a String and then finds all items in that List where any property value matches the string.
So for example we have an object with 3 properties, I pass a list of this object to the method and a search string and it returns back a list of objects where any of the properties may contain the search string.
I can do like this :
var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
But how can I make this method Generic so I can pass any List of Objects to it ?
Thanks in advance...
If you are using Dynamic Linq, try this
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var propsToCheck = typeof (T).GetProperties().Where(a => a.PropertyType == typeof(string));
var filter = propsToCheck.Aggregate(string.Empty, (s, p) => (s == string.Empty ? string.Empty : string.Format("{0} OR ", s)) + string.Format("{0} == #0", p.Name));
var filtered = source.AsQueryable().Where(filter, searchStr);
return filtered;
}
Use Type.GetProperties() to get all the properties of an object. Use PropertyInfo.GetValue() to get the value of a given property in a given object. You need to figure out how you want a match your string to a DateTime, to numbers, or to other complex objects. Put it all into a function like bool IsMatch(this object obj, string val). Then you can filter your list like list.Where(x => x.IsMatch("something")).
Here you go mate:
private static void Main(string[] args)
{
var list = new List<object> {new {prop1 = "A", prop2 = "B"},new {prop3 = "B", prop4 = "C"}};
var subList = SearchForStringInProperties(list, "C");
}
private static IEnumerable<object> SearchForStringInProperties(IEnumerable<object> list, string searchString)
{
return from obj in list where FindStringInObjProperties(obj, searchString) select obj;
}
private static bool FindStringInObjProperties(object obj, string searchString)
{
return obj.GetType().GetProperties().Any(property => obj.GetType().GetProperty(property.Name).GetValue(obj).ToString().Equals(searchString));
}
If you just want to match the properties with same type as your argument, this extension method can help,
public static class ListExtensions
{
public static IEnumerable<T> MatchWithAnyProperty<T, TK>(this IEnumerable<T> list, TK value)
{
var argType = typeof (TK);
var properties = typeof(T).GetProperties().Where(x => x.PropertyType.IsAssignableFrom(argType));
return list.Where(item => properties.Any(prop =>
{
var propertyValue = prop.GetValue(item, null);
if (value == null)
return propertyValue == null;
return propertyValue.Equals(value);
}));
}
}
This can be used like,
var items = new[]
{
new
{
Name = "Test",
Age = 20,
Test=25
},
new
{
Name = "Hello",
Age = 10,
Test=15
},
new
{
Name = "T2gdhest",
Age = 14,
Test=20
},
new
{
Name = "hai",
Age = 33,
Test=10
},
new
{
Name = "why not",
Age = 10,
Test=33
},
};
var match= items.MatchWithAnyProperty(10);
foreach (var item in match)
{
Console.WriteLine(item.Name);
}
Console.ReadKey();
And there is the old way ...
public static IList<T> MyMethod<T>(IList<T> myList, string filter)
{
if (myList == null) return null;
if (filter == null) return myList;
var tfilter = filter.GetType();
var properties = typeof(T).GetProperties().Where(x => x.PropertyType.FullName == typeof(string).FullName);
if (!properties.Any()) return null;
var res = new List<T>();
foreach(var el in myList)
{
foreach(var p in properties)
{
if ((string)p.GetValue(el) == filter)
{
res.Add(el);
break;
}
}
}
return res;
}