How to call a Generic method with dynamic properties in C# - c#

I have the a few methods that have similar signature and was trying to convert them into one generic one without the use of interfaces.
public List<MultiSelectDropdown> ConvertListOfJobStatusToDropdownListClickable(List<JobStatus> js) {
var list = new List<MultiSelectDropdown>();
if (js != null && js.Count >= 1) {
list = js.Select(item => new MultiSelectDropdown { Name = item.StatusValue, Value = item.id.ToString() }).ToList();
}
return list;
}
public List<MultiSelectDropdown> ConvertListOfCUsersToDropdownListClickable(List<cUser> users) {
var list = new List<MultiSelectDropdown>();
if (users != null && users.Count >= 1) {
list = users.Select(item => new MultiSelectDropdown { Name = item.User_Name, Value = item.Id.ToString() }).ToList();
}
return list;
}
This is what I would like to do; pass in a list with two properties.
List<MultiSelectDropdown> ddlForClientUsers = ConvertToMultiSelectDropdownList(listOfClientsForUser, n => n.Client_Id, v => v.Client);
List<MultiSelectDropdown> ddlForJobStatus = ConvertToMultiSelectDropdownList(listOfJobStatus, n => n.Id, v => v.JobName);
This is the method I have tried but not sure how to get item.propName and item.propValue to work.
I get "Cannot resolve" propName and propValue in the method below
Is this possible?
public List<MultiSelectDropdown> ConvertToMultiSelectDropdownList<T, TPropertyName, TPropertyValue>(List<T> listOfT, Func<T, TPropertyName> propName, Func<T, TPropertyValue> propValue) {
var list = new List<MultiSelectDropdown>();
if (listOfT != null && listOfT.Count >= 1) {
list = listOfT.Select(item => new MultiSelectDropdown { Name = item.propName, Value = item.propValue }).ToList();
}
return list;
}
public class MultiSelectDropdown {
public string Name { get; set; }
public string Value { get; set; }
public bool IsChecked { get; set; }
}

Because the properties of your MultiSelectDropdown are strings, your functions should return those as well. And to invoke the functions, you have to write them like propName(item) instead of item.propName - that is the property syntax, and you indicated you didn't want to use interfaces.
public List<MultiSelectDropdown> ConvertToMultiSelectDropdownList<T>(List<T> listOfT, Func<T, string> propName, Func<T, string> propValue) {
var list = new List<MultiSelectDropdown>();
if (listOfT != null && listOfT.Count >= 1) {
list = listOfT.Select(item => new MultiSelectDropdown { Name = propName(item), Value = propValue(item) }).ToList();
}
return list;
}

You are really close, with just a slight mistake. The line (reformatted to prevent scrolling):
list = listOfT.Select(item => new MultiSelectDropdown
{
Name = item.propName,
Value = item.propValue
}).ToList();
needs to be:
list = listOfT.Select(item => new MultiSelectDropdown
{
Name = propName(item),
Value = propValue(item)
}).ToList();

Related

Is it possible to add GroupBy to a IQueryable<T> query?

I am trying to genericize some repeated stuff but struggling with this. I don't even know if it's even possible. I put what I need inside the code block but I'd be happy to clarify if need be. Any help is appreciated.
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
public static object GetPropertyValue(this object obj, string name)
{
foreach (string part in name.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
public static T GetPropertyValue<T>(this object obj, string name)
{
object retval = GetPropertyValue(obj, name);
if (retval == null) { return default(T); }
// throws InvalidCastException if types are incompatible
return (T)retval;
}
#endregion
Thanks coder_b, I used another response from the link you provided and got what I am looking for. Here is the answer if anyone else needs it:
public static async Task<object> LoadData<T>(IQueryable<T> query, string RequestGroup) where T : class
{
//this works
//var Groupped = query.AsNoTracking().ToList().GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() });
//How can I achieve this?
//var Groupped = query.GroupBy(x => x.GetPropertyValue(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).AsNoTracking().ToListAsync();
//Answer
var Groupped = query.GroupBy(GetGroupKey<T>(RequestGroup)).Select(x => new { key = x.Key, count = x.Distinct().Count() }).ToList();
return new { Data = Groupped };
}
public async Task<JsonResult> GetData()
{
var query = _context.Samples.AsQueryable();
return Json(await LoadData(query, "Description"));
}
public class Sample
{
public int SampleID { get; set; }
public string Description { get; set; }
}
#region Extensions
private static Expression<Func<T, string>> GetGroupKey<T>(string property)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<T, string>>(body, parameter);
}
#endregion

Multiple same elements in the list, how to display only once, no duplicates?

My List contains multiple subjects, but some of them are the same. How to display all of them but without repetitions?
public class Subject
{
public string SubjectName { get; set; }
public Subject(string subjectName)
{
this.SubjectName = subjectName;
}
}
List<Subject> listOfSubjects = new List<Subject>();
string subject = "";
Console.WriteLine("Enter the name of the subject");
subject = Console.ReadLine();
listofSubjects.Add(new Subject(subject));
string pastSubject = "";
foreach (Subject sub in listOfSubjects)
{
if (sub.SubjectName != pastSubject)
{
Console.WriteLine(sub.SubjectName);
}
pastSubject = sub.SubjectName;
}
One solution can be to build a IEqualityComparer<Subject> for the Subject class. Below is code for it.
public class SubjectComparer : IEqualityComparer<Subject>
{
public bool Equals(Subject x, Subject y)
{
if (x == null && y == null)
{
return true;
}
else if (x == null || y == null)
{
return false;
}
else
{
return x.SubjectName == y.SubjectName;
}
}
public int GetHashCode(Subject obj)
{
return obj.SubjectName.GetHashCode();
}
}
Then just call the System.Linq Distinct function on it supplying the IEqualityComparer instance.
List<Subject> distinctSubjects = listOfSubjects.Distinct(new SubjectComparer()).ToList();
The resultant distinctSubjects list is Distinct.
If you use Linq then you can use GroupBy to get the distinct Subjects by name.
var distinctList = listOfSubjects
.GroupBy(s => s.SubjectName) // group by names
.Select(g => g.First()); // take the first group
foreach (var subject in distinctList)
{
Console.WriteLine(subject.SubjectName);
}
This method returns IEnumerable<Subject>, ie a collection of the actual Subject class.
I created a fiddle.
You can get distinct subject names with
var distinctSubjectNames = listOfSubjects
.Select(s => s.SubjectName)
.Distinct();
foreach (string subjectName in distinctSubjectNames) {
Console.WriteLine(subjectName);
}

C# ToDictionary get Anonymous Type value before C # 7

Hello here is how i get value from dictionary myage value after C# 7
static void Main(string[] args)
{
List<User> userlist = new List<User>();
User a = new User();
a.name = "a";
a.surname = "asur";
a.age = 19;
User b = new User();
b.name = "b";
b.surname = "bsur";
b.age = 20;
userlist.Add(a);
userlist.Add(b);
var userlistdict = userlist.ToDictionary(x => x.name,x=> new {x.surname,x.age });
if(userlistdict.TryGetValue("b", out var myage)) //myage
Console.WriteLine(myage.age);
}
}
public class User {
public string name { get; set; }
public string surname { get; set; }
public int age { get; set; }
}
Okey result is:20
But Before C# 7 how can i get myage value from dictionary. I could't find any other way.Just i found declare myage in trygetvalue method.
Three options:
First, you could write an extension method like this:
public static TValue GetValueOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
TKey key)
{
TValue value;
dictionary.TryGetValue(dictionary, out value);
return value;
}
Then call it as:
var result = userlist.GetValueOrDefault("b");
if (result != null)
{
...
}
Second, you could use var with out by providing a dummy value:
var value = new { surname = "", age = 20 };
if (userlist.TryGetValue("b", out value))
{
...
}
Or as per comments:
var value = userlist.Values.FirstOrDefault();
if (userlist.TryGetValue("b", out value))
{
...
}
Third, you could use ContainsKey first:
if (userlist.ContainsKey("b"))
{
var result = userlist["b"];
...
}
Another option is to store the User object as the dictionary item value instead of the anonymous type and then you can declare the type first and use that in the TryGetValue.
var userlistdict = userlist.ToDictionary(x => x.name, x => x );
User user;
if (userlistdict.TryGetValue("b", out user))
{
Console.WriteLine(user.surname);
Console.WriteLine(user.age);
}
The first line of creating the Dictionary from the list is same as
var userlistdict = userlist.ToDictionary(x => x.name);

How to use attribute to map properties

I have the following code.
public class SyncProperty : Attribute
{
public readonly string PropertyName;
public SyncProperty(string propertyName)
{
this.PropertyName = propertyName;
}
}
public class SyncContact
{
[SyncProperty("first_name")]
public string FirstName { get; set; }
[SyncProperty("last_name")]
public string LastName { get; set; }
[SyncProperty("phone")]
public string Phone { get; set; }
[SyncProperty("email")]
public string Email { get; set; }
}
I need to create an instance of my SyncContact such as
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
And then with that object I need to create a NameValueCollection where the object's property uses the SyncProperty's PropertyName as the Key in the collection. Then use that to make a post request to an API. So in this case I would end up with a collection like...
collection["first_name"] = "Test"
collection["last_name"] = "Person"
collection["phone"] = "123-123-1234"
collection["email"] = "test#test.com"
How can I do that?
If you want to get some type metadata, you should use Reflection. To read attribute you can use GetCustomAttribute<AttributeType>() extension for MemberInfo.
This extension method builds sync dictionary from type properties decorated with SyncPropertyAttribute (I suggest to use the dictionary instead of NameValueCollection):
public static Dictionary<string, string> ToSyncDictionary<T>(this T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new {
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
Usage:
var collection = contact.ToSyncDictionary();
Output:
{
"first_name": "Test",
"last_name": "Person",
"phone": "123-123-1234",
"email": "test#test.com"
}
Note: if you are going to use contact data in POST request, then you should consider using simple JSON serialization attributes instead of creating your own attributes. E.g. with Json.NET:
public class SyncContact
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
}
Then simple serialization will do the job:
string json = JsonConvert.SerializeObject(contact);
And the result will be exactly same as above.
The attributes belong to the properties of the class, so you need to get the type of the class, then find the appropriate properties and then get the custom attributes.
Something like:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
var t = typeof(SyncContact);
var props = t.GetProperties().Select(p => new { p, attr = p.GetCustomAttribute<SyncProperty>() }).Where(p => p.attr != null);
var dict = props.ToDictionary(p => p.attr.PropertyName, v => v.p.GetValue(contact) );
This leaves out any properties that aren't tagged, but you can decide to handle those differently if you wanted to.
Fiddle
Assuming SyncProperty is tagged on every property, this should do the job:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test#test.com"};
var collection = contact.GetType().GetProperties()
.Select(x => new
{
x.GetCustomAttribute<SyncProperty>().PropertyName,
Value = x.GetValue(contact).ToString()
})
.ToDictionary(x => x.PropertyName, x => x.Value);
As a helper method:
public static class SynxHelper
{
public static Dictionary<string, string> Serialize<T>(T obj)
{
return typeof(T).GetProperties()
.Select(x => new
{
SyncProperty = x.GetCustomAttribute<SyncProperty>(),
Value = x.GetValue(obj)
})
.Where(x => x.SyncProperty != null)
.ToDictionary(x => x.SyncProperty.PropertyName, x => x.Value.ToString());
}
}
// usage
var collection = SynxHelper.Serialize(contact);
Here is a one line linq solution for this
(from prop in obj.GetType().GetProperties()
where prop.GetCustomAttribute<SyncProperty>() != null
select new { Key = prop.GetCustomAttribute<SyncProperty>().PropertyName, Value = prop.GetValue(obj) })
.ToDictionary(k => k.Key, v => v.Value);
BUT!!!!!!!! Don't try doing this your self. This is not optimized and slow as all reflection is.
This is just to demonstrate how bad reflection is
static void Main(string[] args)
{
var data = Enumerable.Range(0, 10000).Select(i => new SyncContact { FirstName = "Test", LastName = "Person", Phone = "123-123-1234", Email = "test#test.com" }).ToArray();
Stopwatch sw = new Stopwatch();
long m1Time = 0;
var total1 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary(item);
total1++;
}
sw.Stop();
m1Time = sw.ElapsedMilliseconds;
sw.Reset();
long m2Time = 0;
var total2 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary2(item);
total2++;
}
sw.Stop();
m2Time = sw.ElapsedMilliseconds;
Console.WriteLine($"ToSyncDictionary : {m1Time} for {total1}");
Console.WriteLine($"ToSyncDictionary2 : {m2Time} for {total2}");
Console.ReadLine();
}
public static IDictionary<string, string> ToSyncDictionary<T>(T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new
{
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
public static IDictionary<string, string> ToSyncDictionary2<T>(T value)
{
return Mapper<T>.ToSyncDictionary(value);
}
public static class Mapper<T>
{
private static readonly Func<T, IDictionary<string, string>> map;
static Mapper()
{
map = ObjectSerializer();
}
public static IDictionary<string, string> ToSyncDictionary(T value)
{
return map(value);
}
private static Func<T, IDictionary<string, string>> ObjectSerializer()
{
var type = typeof(Dictionary<string, string>);
var param = Expression.Parameter(typeof(T));
var newExp = Expression.New(type);
var addMethod = type.GetMethod(nameof(Dictionary<string, string>.Add), new Type[] { typeof(string), typeof(string) });
var toString = typeof(T).GetMethod(nameof(object.ToString));
var setData = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select Expression.ElementInit(addMethod,
Expression.Constant(name),
Expression.Condition(Expression.Equal(Expression.Property(param, p), Expression.Constant(null)),
Expression.Call(Expression.Property(param, p), toString),
Expression.Constant(null,typeof(string))));
return Expression.Lambda<Func<T, IDictionary<string, string>>>(Expression.ListInit(newExp, setData), param).Compile();
}
}
On my machine I got a 10x boost in pers.
If you can use some serializer like JSON.net since you will need to change a lot of things to make it work well and you already have a tone of stuff that dose it for you.

Anonymous Delegate - Search property with collection of objects

Here is the current code in my application using an anonymous delegate to search a collection on properties:
public class MyObject
{
public MyObject() { }
public string MyObjectId { get; set; }
public List<MySubObject> SubObjects { get; set; }
}
public class MySubObject
{
public MySubObject() { }
public string MySubObjectId { get; set; }
}
public List<MyObject> Search(string input)
{
List<MyObject> AllObjects = Data.GetAllObjects();
List<MyObject> SearchResults = new List<MyObject>();
SearchResults = AllObjects.FindAll
(
delegate(MyObject o)
{
return e.MyObjectId.Equals(input)
}
);
if (SearchResults .Count.Equals(0))
SearchResults = null;
return SearchResults ;
}
I want to modify the anonymous delegate to search by the MySubObject.MySubObjectId property in the generic list instead of the MyObjectId property. How would I modify the code in the anonymous delegate to accomplish this?
Try the following
delegate(MyObject o) {
var mySubObject = o as MySubObject;
return mySubObject != null && mySubObject.MySubObjectId == input;
}
Note that you could also use shorter lambda syntax here
(o) => {
var mySubObject = o as MySubObject;
return mySubObject != null && mySubObject.MySubObjectId == input;
}
Or a LINQ query
var searchResult = AllObjects
.OfType<MySubObject>()
.Where(x => x.MySubObjectId == input)
.Cast<MyObject>()
.ToList()
Try this, using lambda expressions. Basically an object is found if at least one of its subobjects contains the required input.
SearchResults = AllObjects.Where(obj =>
obj.SubObjects != null
&& obj.SubObjects.Any(subObj => ubObj.MySubObjectId.Equals(input))
).ToList();
This looks like it could be trimmed down a lot, but you need to get rid of null assignments to the lists:
public List<MyObject> Search(string input)
{
return Data.GetAllObjects()
.Where(obj => obj.SubObjects
.Any(subobj => subobj.SubOjectId.Equals(input)));
}
.Clear() your lists instead of nullifying them, for a more consistent design and a lot less null-checking.

Categories