Invoke a static class list by reflection C# - c#

I have a series of C# static lists in an API project that are very similar to the simple example defined here.
using System.Collections.Generic;
namespace myproject.api.PropModels
{
public class CommonSelectOptionsYesNoItem
{
public int Id { get; set; }
public string Title { get; set; }
}
public static class CommonSelectOptionsYesNo
{
public static readonly List<CommonSelectOptionsYesNoItem> Table = new List<CommonSelectOptionsYesNoItem>
{
new CommonSelectOptionsYesNoItem { Id = 0, Title = "No",},
new CommonSelectOptionsYesNoItem { Id = 1, Title = "Yes",},
};
}
}
These models establish a common information reference between a Javascript web application and the API that services the application.
User's are uploading spreadsheet data to the API that includes the list class name and the Title of an item in the list. I need to be able to determine what Id is associated with the Title - if any.
For example I know that this the information is in the list CommonSelectOptionsYesNo.Table and the Title property is "Yes". I can therefore determine that the the Id is 1.
In principle I can set up a switch / case method that picks the list identified as CommonSelectOptionsYesNo.Table and then gets the Id value. There are however than 60 of these reference lists and they keep growing.
Can I use reflection to invoke an instance of the readonly static list based on the the static class object name - in this example CommonSelectOptionsYesNo.Table?

After further research have worked out the following method to call up the static readonly list and return the Id for any given "Title" value.
The propModelKey is stored with the static list class in a dictionary of all the lists.
The list can be extracted as an object - knowing that the list is always declared with the property name "Table".
The properties of the list objects can vary depending on the purpose of the list but they always have the "Id" and "Title" properties. Serializing and deserializing the object with the simple class object "SelectOptions" generates a list that can be queried to extract the Id corresponding to the Title string submitted.
// This will return an Id of 1 from the simple YesNo list
var id = GetSelectListIndex("QuestionOneId", "Yes");
// Method to extract the Id of a value in a list given the list model key
private static int? GetSelectListIndex(string propModelKey, string title)
{
if (SelectListModelMap.ContainsKey(propModelKey))
{
var model = SelectListModelMap[propModelKey];
var typeInfo = Type.GetType("myproject.api.PropModels." + model).GetTypeInfo();
var fieldInfo = typeInfo.DeclaredFields.FirstOrDefault(x => x.Name == "Table");
var json = JsonConvert.SerializeObject(fieldInfo.GetValue(new object()));
var dictionary = JsonConvert.DeserializeObject<List<SelectOptions>>(json);
var index = dictionary.FirstOrDefault(x => x.Title == title)?.Id;
return index;
}
return null;
}
// Dictionary of lists with model key and class name
public static Dictionary<string, string> SelectListModelMap => new Dictionary<string, string>
{
{ "QuestionOneId", "CommonSelectOptionsYesNo" },
{ "CountryId", "CommonSelectOptionsCountries" },
// ... other lists
};
// generic class to extract the Id / Title pairs
public class SelectOptions
{
public int Id { get; set; }
public string Title { get; set; }
}

Related

How to access the object's specific properties from a list of objects sharing the same interface

I have an application where i have say 10 objects of different types. I wish to have them in same list and iterate through them on many occasions. I cant push them into one list because they are of different types. So i created an interface and created a property that all objects share. Now i have the list of objects and type of the list is the "interface". When i iterate through the object, i can't access the specific properties of the object because the compiler will only know at runtime what object it is. So if i try to code Object_A.Name, visual studio will show error because it doesn't know they type of object. I can obviously do an if else or something similar to find the type of object and cast it, but i want to know of there is a better way, or if this whole approach of having an interface is wrong and if i should have begun in a different direction.
In the code below, i want to get the Devname, which i can't because its not part of the interface, but belongs to every object. I could make it part of the interface, but every now and then i may need to get a specific property. hence wanting to know if there is a way to do it.
foreach (ICommonDeviceInterface device in Form1.deviceList)
{
if (device.DevName.Equals(partnername))
{
return device.Port[portNo].PortRef;
}
}
One way you could do this is by using reflection to try to get the property value of a named property from an object, using a helper method like:
public static object GetPropValue(object src, string propName)
{
return src?.GetType().GetProperty(propName)?.GetValue(src, null);
}
Credit for above code goes to: Get property value from string using reflection in C#
This requires no checking types or casting, it just returns the value of the property, or null if it doesn't contain the property.
In use it might look like:
private static void Main()
{
// Add three different types, which all implement the same interface, to our list
var devices = new List<ICommonDeviceInterface>
{
new DeviceA {DevName = "CompanyA", Id = 1},
new DeviceB {DevName = "CompanyB", Id = 2},
new DeviceC {Id = 3},
};
var partnerName = "CompanyB";
foreach (var device in devices)
{
// Try to get the "DevName" property for this object
var devName = GetPropValue(device, "DevName");
// See if the devName matches the partner name
if (partnerName.Equals(devName))
{
Console.WriteLine($"Found a match with Id: {device.Id}");
}
}
}
Classes used for the sample above:
interface ICommonDeviceInterface
{
int Id { get; set; }
}
class DeviceA : ICommonDeviceInterface
{
public int Id { get; set; }
public string DevName { get; set; }
}
class DeviceB : ICommonDeviceInterface
{
public int Id { get; set; }
public string DevName { get; set; }
}
class DeviceC : ICommonDeviceInterface
{
public int Id { get; set; }
}
Use "as" and "is" to know what type of interface
public class A : ICommonDeviceInterface
{
public int AMember;
}
public class B :ICommonDeviceInterface
{
public int BMember;
}
foreach (ICommonDeviceInterface device in Form1.deviceList)
{
if(device is A)
{
A a = device as A;
a.AMember = 100;
}
else if(device is B)
{
B b = device as B;
b.BMember = 123;
}
}

Returning results of LINQ query

New to LINQ but this should be fairly simple.
I'm pulling a recordset of products from the DB:
ReportData= topProductsController.GetWowDetails(CurrentUser.UserId, _companyGroupCode, sector, year, string.Empty, string.Empty, string.Empty);
and from that recordset I'm trying to group the results by the product ID and count:
var productCounts = (from record in wowReportData
group record by record.ProductID into grouping
select new topProduct { Name = grouping.Key, quantity = grouping.Count() });
Here's the class I'm trying to return:
public class topProduct
{
public int quantity { get; set; }
public string Name { get; set; }
public topProduct(string productDesc, int downloadCount)
{
this.Name = productDesc;
this.quantity = downloadCount;
}
}
I'm trying to return a list of these from the function. The current error is that:
topProduct does not contain a constructor that takes 0 parameters
The reason it is failing is because you are using the property initializer way to set values to your properties, and at least in the way you called it (new topProduct {...) it will first initialize the object using the default constructor. But you don't have one.
Change to this:
var productCounts = (from record in wowReportData
group record by record.ProductID into grouping
select new topProduct(grouping.Key, grouping.Count()));
Or add a default constructor (which that is what I'd do) and then you can use it as you did
public class topProduct
{
public int quantity { get; set; }
public string Name { get; set; }
//default constructor
public topProduct() {}
public topProduct(string productDesc, int downloadCount)
{
this.Name = productDesc;
this.quantity = downloadCount;
}
}
The use of () if for when you are initializing an object and you call a constructor - () being the default constructor (with no parameters). This one is created automatically in the case you have not created any other constructor. See here about constructors.
Now in C# 3.5 if I'm not mistaking they introduced the ability to initialize properties inline with the initialization of the object and thus to save you the pain of creating a big array of constructors for all the different options. But that is just a nice syntactic sugar for:
var obj = new Class() { Prop1 = "a", Prop2 = 2 };
||
var obj = new Class();
obj.Prop1 = "a";
obj.Prop2 = 2;
Then they even enabled you to remove the empty () (in the case that the constructor you are calling is the default constructor) and you turn out to have: var obj = new Class { Prop1 = "a", Prop2 = 2 }; but you can't do this if you don't have a default constructor like in your original case.

Reflection for composite property names

I have a Dictionary<string, object> which holds a property name as string and it's value as object. I also have a Bind method extension which, through reflection, sets that propery name with its corresponding value:
public static T Bind<T>(this T #this,
Dictionary<string, object> newValues,
params string[] exceptions) where T : class
{
var sourceType = #this.GetType();
foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
{
var property = sourceType.GetProperty(pair.Key,
BindingFlags.SetProperty |
BindingFlags.Public |
BindingFlags.Instance);
var propType = Nullable.GetUnderlyingType(property.PropertyType) ??
property.PropertyType;
property.SetValue(#this, (pair.Value == null) ? null :
Convert.ChangeType(pair.Value, propType), null);
}
return #this;
}
For instance, consider a class like this:
public class User
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
Everything runs fine, except when I got a class with a property name of another object, like this:
public class User
{
public string Name { get; set; }
public DateTime Date { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string PostalCode { get; set; }
}
So, if I try to send a Name property name, ok, but I got problems with composite property names, like Address.PostalCode.
Can you advise a way to handle that situation?
EDIT #1:
To summarize the problem: calling sourceType.GetProperty("Name", ...) in the context of a User class instance correctly allows to set its value, but it doesn't work using a sourceType.GetProperty("Address.PostalCode", ...) in same instance.
EDIT #2:
A more complete example should be:
var user = new User{ Address = new Address() };
var values = new Dictionary<string, object>
{
{ "Name" , "Sample" },
{ "Date" , DateTime.Today },
{ "Address.PostalCode", "12345" } // Here lies the problem
}
user.Bind(values);
My guess is that Convert.ChangeType only works for objects implementing IConvertible. Thus, I'd just add a check, and only use Convert.ChangeType if pair.Value has a type that implements IConvertible. Furthermore, afaik Convert does not use overloaded conversion operators, so you can save this check whenever pair.Value is not a struct, i.e.
object value;
if (pair.Value == null) {
value = null;
} else {
value = pair.Value.GetType().IsStruct ? Convert.ChangeType(pair.Value, propType) : pair.Value;
}
...
There are many binding engines out there, WPF, ASP.NET MVC, winforms in the core .NET and who knows how many others, you can check out all their source codes and documentation about their syntax.
Let's see the most simple case. Let's say that the variable X holds an object and you have the binding expression "A.B.C". Let's split up the binding path, the first part is "A". So you use reflection to get the property named "A" in X, and you put that other object into X. Now comes the second part, "B", so let's find a property named "B" in (the new) X. You find that, and put that into X. Now you get to the final part, "C", and now you can either read or write that property in X. The point is that you don't need recursion or anything, it's just a simple loop, you iterate over the parts of the binding expression, evaluate them, and you keep the current object in the same variable.
But the fact is that it can get much more complex than that. You could ask for array indexing, like "A.B[2].C". Or what if you have a path "A.B", and X.A is null, what do you do? Instantiate X.A, but what if it lacks a public parameterless constructor?
I want you to see that it can be a very complex problem. You have to specify a syntax and rules, and then implement that. You didn't specify in your question the exact syntax and rules you want to use. And if it happens to be more than the simple case I mentioned above, then the solution could be too lengthy.
I was able to solve it identifying if the property name have a period and recurring it:
public static T Bind<T>(this T #this,
Dictionary<string, object> newValues,
params string[] exceptions) where T : class
{
var sourceType = #this.GetType();
var binding = BindingFlags.Public | BindingFlags.Instance;
foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
{
if(pair.Key.Contains("."))
{
var property = sourceType.GetProperty(
pair.Key.Split('.').First(),
binding | BindingFlags.GetProperty);
var value = property.GetValue(#this, null);
value.Bind(new Dictionary<string, object>
{
{
String.Join(".", pair.Key.Split('.').Skip(1).ToArray()),
pair.Value
}
});
}
else
{
var property = sourceType.GetProperty(pair.Key,
binding | BindingFlags.SetProperty);
var propType = Nullable.GetUnderlyingType(property.PropertyType) ??
property.PropertyType;
property.SetValue(#this, (pair.Value == null) ? null :
Convert.ChangeType(pair.Value, propType), null);
}
}
return #this;
}
Usage:
var user = new User {Address = new Address{ User = new User() }};
var values = new Dictionary<string, object>()
{
{"Name", "Sample"},
{"Date", DateTime.Today},
{"Address.PostalCode", "12345"},
{"Address.User.Name", "Sub Sample"}
};
user.Bind(values);
public class User
{
public string Name { get; set; }
public DateTime Date { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string PostalCode { get; set; }
public User User { get; set; }
}

Reorder list by a property I don't have

I have a bog-standard list in C#: List (where Channel is a TV channel). Channel is defined as:
public class Channel
{
public int ChannelId { get; set; }
public string ChannelName { get; set; }
}
My ORM populates a list of Channels, which comes from the database in Channel Id order. I need to re-order the list based on a new custom sort property, say ChannelOrder, but I can't modify the underlying database.
Any thoughts on how to do this, without modifying the underlying DB?
So for instance, if I current have coming from the db:
ChannelId =1, ChannelName = "BBC1", ChannelId =2, ChannelName = "BBC2", ChannelId =3, ChannelName = "ITV"
I might want them ordered as BBC2, BBC1, ITV, basically a custom order.
If you're happy to do this in-process (rather than as part of the query) you can use:
var orderedChannelNames = new[] { "BBC2", "BBC1", "ITV", ... };
var sorted = unsorted.OrderBy(ch => orderedChannelNames.IndexOf(ch.ChannelName));
Alternatively, if you know all the channels in your list will be present:
var map = unsorted.ToDictionary(ch => ch.ChannelName);
var sorted = orderedChannelNames.Select(name => map[name]);
You could maybe use another class that returns an "attached" property, kind of like attached properties in WPF:
public static class ChannelSort
{
static Dictionary<Channel, int> _dict = new Dictionary<Channel, int>();
public static int GetSort(this Channel c)
{
return _dict[c] //will throw if key's not found,
//may want to handle it for more descriptive exception
}
public static int SetSort(this Channel c, int sort)
{
_dict[c] = sort;
}
}
And then you could do this:
var result = unsortedList.OrderBy(c => c.GetSort());

Linq extracting objects

I have a JSON "multi-level" response that I need to deserialize and from the deserialized classes structure I need to extract all the objects of a certain class.
Below the code I'm using, at the end I find that my result is empty, not populated.
// given these two classes:
[DataContract]
public class ThingsList
{
[DataMember(Name = "status")]
public string Status { get; set; }
[DataMember(Name = "since")]
public double Since { get; set; }
[DataMember(Name = "list")]
public Dictionary<string, ThingsListItem> Items { get; set; }
public DateTime SinceDate { get { return UnixTime.ToDateTime(Since); } }
}
[DataContract]
public class ThingsListItem
{
[DataMember(Name = "url")]
public string Url { get; set; }
[DataMember(Name = "title")]
public string Title { get; set; }
}
// I can deserialize my json to this structure with:
ThingsList results = JsonConvert.DeserializeObject<ThingsList>(e.Result);
// now I need to "extract" only the ThingsListItem objects, and I'm trying this:
var theList = from item in results.Items.OfType<ThingsListItem>()
select new
{
Title = item.Title,
Url = item.Url
};
// but "theList" is not populated.
The points here are (I believe):
- I try to use results.Items.OfType() in order to extract only the ThingsListItem objects, that in the "upper" class are declared in the
public Dictionary Items { get; set; }
row.
Any idea? Tell if it's not clear...
Thanks
Andrea
EDIT: updated my response for clarity.
Since your Dictionary values are of type ThingsListItem you can access them directly by using the Dictionary's Values property. There is no need to use OfType to check their type and extract them. Simply use:
var items = results.Items.Values;
The Values property would return an ICollection<ThingsListItem>. You can then iterate over the results with a foreach. LINQ does not have to be used.
While the Values property described above should be sufficient, I will point out a few issues with your original LINQ query attempt.
1) The following query is probably what you were after. Again, the Dictionary's Values property is key (no pun intended) to accessing the items:
var theList = from item in results.Items.Values
select new
{
Title = item.Title,
Url = item.Url
};
2) Why are you using new? That will return an IEnumerable of anonymous types. You already have a defined class, so why project into a new anonymous type? You should retain the underlying ThingsListItem items by selecting the item directly to get an IEnumerable<ThingsListItem>:
var theList = from item in results.Items.Values
select item;
foreach (var item in theList)
{
Console.WriteLine("Title: {0}, Url: {1}", item.Title, item.Url);
}
You would usually project into a new anonymous type to define a type with data properties you are interested in. Generally you would use them immediately after the query, whereas a selection into an existing class could be used immediately or passed around to other methods that are expecting that type.
Hopefully this has cleared up some questions for you and you have a better idea of using LINQ and when to use the new keyword. To reiterate, for your purposes it seems the Values property should suffice. Using LINQ to select the item is redundant when there are other immediate means to do so.

Categories