Reflection to get and use class properties - c#

I am trying to update a linked list from a datagridview using reflection so I don't have to write a line of code for each property.
The class:
public class clsUnderlying
{
public int UnderlyingID { get; set; }
public string Symbol { get; set; }
public double RiskFreeRate { get; set; }
public double DividendYield { get; set; }
public DateTime? Expiry { get; set; }
}
One line of code per property works:
UdlyNode.Symbol = (string)GenericTable.Rows[IDX].Cells["Symbol"].Value;
UdlyNode.Expiry = (DateTime)GenericTable.Rows[IDX].Cells["Expiry"].Value;
etc.
But there are many classes and class properties, so I'd prefer to use a loop and reflection, but I'm not sure how, and my attempt below has errors.
PropertyInfo[] classProps = typeof(GlobalVars.clsUnderlying).GetProperties();
foreach (var Prop in classProps)
{
Type T = GetType(Prop); // no overload for method GetType
UdlyNode.Prop.Name = Convert.T(GenericTable.Rows[IDX].Cells[Prop.Name].Value); // error on "Prop.Name" and "T.("
}
Thanks for any suggestions or links to further my understanding.

Reflection-based loop needs to use a different syntax:
Property type is a property of PropertyInfo,
Convert has a ChangeType method that takes System.Type, and
Property assignment needs to be done by calling SetValue
Therefore, your loop would look like this:
foreach (var p in classProps) {
p.SetValue(
UdlyNode
, Convert.ChangeType(
GenericTable.Rows[IDX].Cells[p.Name].Value
, p.PropertyType
)
);
}

I would suggest to use BindingSource. This way a changed value in the Grid will automatically be changed in your list:
BindingSource bs = new BindingSource();
bs.DataSource = yourList;
dataGridView1.DataSource = bs;
This would solve the case where you want to update values manually changed in the grid.

Related

Reflection - SetValue from deep context

I am facing an issue, surely due to my lack of knowledge in the reflection process, while trying to set a "complex" class hierarchy based on Json files.
Here are my main model :
public class Names
{
public Weapons Weapons { get; set; }
public Armors Armors { get; set; }
public Utilities Utilities { get; set; }
public Names()
{
Weapons = new Weapons();
Armors = new Armors();
Utilities = new Utilities();
}
}
Each of them having a list of sub-model like this:
public class Weapons
{
public BattleAxe BattleAxe { get; set; } = new BattleAxe();
public Bomb_Missile Bomb_Missile { get; set; } = new Bomb_Missile();
// etc... Around 20 to 25
}
And finally the ended model which is the exact equivalent of each json files but may have very different properties :
public class BattleAxe
{
public string[] Normal { get; set; } = new string[0];
public string[] DescriptiveAdjective { get; set; } = new string[0];
public string[] Material { get; set; } = new string[0];
public string[] Type { get; set; } = new string[0];
public string[] Title { get; set; } = new string[0];
public string[] Of { get; set; } = new string[0];
public string[] NormalForTitle { get; set; } = new string[0];
}
Since the MS Json deserializer does not support the conversion to a $type as Newtonsoft before, I tried to populate the values using reflection too like this (I've removed all the null-check for code readability) :
public static void Load()
{
Names = new Names();
foreach (var category in Names.GetType().GetProperties())
{
if (category is not null && !(category.GetGetMethod()?.IsStatic ?? false))
{
var categoryType = category.PropertyType;
foreach (var item in category.PropertyType.GetProperties())
{
var itemType = item.PropertyType;
var subTypeData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(File.ReadAllText($"./Assets/Names/{categoryType.Name}/{itemType.Name}.json"));
var concreteObj = Activator.CreateInstance(itemType);
foreach (var key in subTypeData.Keys)
{
if (itemType.GetProperty(key) is not null && concreteObj is not null)
{
var prop = concreteObj.GetType().GetProperty(key);
var convertedValue = ConvertJsonType(subTypeData[key], subTypeData[key].ValueKind, out var isReferenceType);
// It fails here
prop.SetValue(
isReferenceType ? convertedValue : null,
!isReferenceType ? convertedValue : null
);
}
}
item.SetValue(concreteObj, null);
}
}
}
}
So it fails at the prop.SetValue(...) of the deepest object in the hierarchy with a different error depending on the type of value to set.
If it is a reference, it throws a System.Reflection.TargetException : 'Object does not match target type' Exception
And if it is value, it throw a System.Reflection.TargetException : 'Non-static method requires a target.'
Knowing that I do not have problems around the deserialization as shown here, only the fact that I use a dynamic type (and my instinct tells me it is actually the problem...)
I do not add the ConvertJsonType(...) body as it is functional and really simple
I am more interested in the 'why' than the 'how' so if you can explain me the 'theory' behind the problem, that would help quite a lot :)
Thank you!
PS: I know I can simplify the things in a more readable/performant way but I must achieve it with reflection for personal learning :)
Same for the System.Text.Json namespace, I do not intend to switch back to Newtonsoft for that
When calling SetValue(instance, value) you should pass the object which property should be set.
It's a wild guess, but you could try this:
prop.SetValue(concreteObj,
!isReferenceType ? convertedValue : null);
Because you want to fill the properties of concreteObj, not the value it self.
If you look at the object prop it was a return value of concreteObj.GetType().GetProperty(key);. If you look at it close, The GetProperty is a method from Type which isn't bound to any instance. So that's why you need to pass the instance of the object as the first parameter.
I mean this in a positive way: The itemType.GetProperty(key) is called every iteration, it will be the same value each iteration, you could bring it before the loop.
As docs state TargetException is thrown when:
The type of obj does not match the target type, or a property is an instance property but obj is null.
Passing null for obj in SetValue is valid when you are trying to set value for static property, not an instance one. Property type being a reference one has nothing to do with property being instance or static one so your call should look something like:
prop.SetValue(concreteObj, convertedValue);
Also your item.SetValue(concreteObj, null); does not look right cause concreteObj should be second argument in this call. Something like this:
item.SetValue(Names, concreteObj);
Also if you want only instance properties you can provide BindingFlags to get only instance properties:
foreach (var category in Names.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
Also I would say that category is not null check is redundant so in pair with providing BindingFlags you should remove the if completely.

How add dynamically a list form one object to another?

I have a class that contains a list. I want to copy that list to another object that contains the same type and amount of attributes.
List<CinemaUnitSchema> cinemaUnitSchemas = new List<CinemaUnitSchema>();
foreach (CinemaUnit cinemaUnits in scenario.CinemaUnits)
{
cinemaUnitSchemas.Add(new CinemaUnitSchema
{
Name = cinemaUnits.Name,
AttendantPoints = cinemaUnits.AttendantPoints,
ShowPoints = cinemaUnits.ShowPoints
});
}
scenarioSchema.CinemaUnits.AddRange(CinemaUnitSchemas);
However, I'm receiving an error in this line of code;
AttendantPoints = cinemaUnits.AttendantPoints
The error I'm receiving is:
"Cannot implicitly convert type 'System.Collections.Generic.List < MyApp.Models.AttendantPoint >' to 'System.Collections.Generic.List < MyApp.Schemas.AttendantPointSchema >'."
Class of CinemaUnit is:
public class CinemaUnit
{
public string Name { get; set; }
public List<AttendantPoint> AttendantPoints { get; set; }
public bool ShowPoints { get; set; }
}
The Class of CinemaUnitSchema is:
public class CinemaUnitSchema
{
public string Name { get; set; }
public List<AttendantPoint> AttendantPoints { get; set; }
public bool ShowPoints { get; set; }
}
Solution Intended
Add in each iteration the respective list to the new object.
Thanks,
You can write a Copy method that makes a shallow copy using reflection.
void Copy(object from, object to)
{
var dict = to.GetType().GetProperties().ToDictionary(p => p.Name, p => p);
foreach(var p in from.GetType().GetProperties())
{
dict[p.Name].SetValue(to, p.GetValue(from,null), null);
}
}
What you actually need it's a way to convert AttendantPoint to AttendantPointSchema.
Solution 1: You can use AutoMapper framework to do it.
Solution 2: You can write generic converter like #Eser suggested.
Solution 3: You can crate converter manually for each class using extension methods, implicit or explicit operators or just write helper class with static functions.
Not sure if this is the problem, but it is probably a good bet that it is.
You are using a foreach statement with the camel case cinemaUnits but when you are trying to copy the fields you are using the title case CinemaUnits instead of the variable with camel case.

Is it possible to get value of a method thru Property

I have to export data to Excel programmatically. I have a class with several properties. I was wondering if it's possible to retrieve values of all properties using a loop. For instance:
public class SqueezeProperties
{
public int WidthField { get; set; }
public string Width_unit { get; set; }
public int ResPressure { get; set; }
public int DensityField { get; set; }
......
}
While writing to excel, I code as:
t = typeof(SqueezeProperties);
foreach (PropertyInfo field in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
oSheet.Cells[r, c++] = field.Name;
Now to input values, is there any way that I can iterate and find values of all properties that can be accessed, and store them in excel?
I doubt if it is even possible. I just taught myself how to access the property name & its details, so I thought maybe the other thing could also be possible and I am simply unaware of it.
Thanks
You can use PropertyInfo.GetValue.
(However according to specification the order of your properties is not guaranteed to be the same as the definition order. So to be safe you might want to order them.)
Also instead of getting them via reflection you could create a collection manually instead, this would take care of the order already.
e.g.
var properties = new Expression<Func<SqueezeProperties, object>>[]
{
o => o.WidthField,
o => o.Width_unit,
//...
};
foreach (var exp in properties)
{
var mem = (MemberExpression)exp.Body;
var prop = (PropertyInfo)mem.Member;
oSheet.Cells[r, c++] = prop.GetValue(squeezePropertiesInstance, null);
}

Pass Property to the Method in C#

I need to pass selection of properties of some types(one type each time), assume this is my type:
public class Product {
[PrimaryKey]
public long Id { get; set; }
[DisplayName("Name")]
public string Title { get; set; }
[Foreignkey(Schema = "Products", Table = "MajorCategory", Column = "Id")]
[DisplayName("MCat")]
public string MajorCategory { get; set; }
[Foreignkey(Schema = "Products", Table = "Category", Column = "Id")]
[DisplayName("Cat")]
public string Category { get; set; }
public long CategoryId { get; set; }
[BoolAsRadio()]
public bool IsScanAllowed { get; set; }
}
So I need a way to pass the list of properties of this type to other Type(Target Type), and use property name, and attributes, and I don't need values, something like the following Pseudo-code:
List<Property> propertyList = new List<Property>();
propertyList.Add(Product.Id);
PropertyList.Add(Product.Title);
TargetType target = new TargetType();
target.Properties = propertyList;
public class TargetType {
public List<Property> Properties { get; set;}
GetAttributes() {
foreach(Property item in Properties){
Console.WriteLine(item.Name)
//Get Attributes
}
}
}
Is there any way to pass just like Product.Id and use name and attributes of that? I don't sure but maybe PropertyInfo can help, I think just can pass List of Object but in that case I can't use attributes and names, what is your suggestion to handle this? or something like this? if I am wrong at all so how can I implement it?
Funny, I was just answering a similar question, or at least I think it is.
It looks like you're trying to concatenate the properties of two types into one? You need an ExpandoObject:
http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject%28v=vs.100%29.aspx
For an implementation of a nested merge, see this:
C# deep/nested/recursive merge of dynamic/expando objects
Basically, you want a keyed list of properties, to start from. The following code will do that for any .NET object:
var props = object.GetType().GetProperties().ToDictionary<PropertyInfo, string>(prop => prop.Name);
And after that it depends on what precisely it is you want to achieve - a true copy of the object, merge with another, or just maintaining the list.
You can make use of reflection in .NET here:
List<PropertyInfo> propertyList = new List<PropertyInfo>();
Type productType = typeof (Product);
propertyList.Add(productType.GetProperty("Id"));
propertyList.Add(productType.GetProperty("Title"));
TargetType target = new TargetType();
target.Properties = propertyList;
public class TargetType {
public List<PropertyInfo> Properties { get; set;}
List<object> GetAttributes()
{
List<object> attributes = new List<object>();
foreach(PropertyInfo item in Properties)
{
Console.WriteLine(item.Name);
attributes.AddRange(item.GetCustomAttributes(true));
}
return attributes;
}
}
You can use a list of PropertyInfo, List<PropertyInfo> as the type of your TargetType .Properties. To get the properties you can try it like this using Reflection.
targetType.Properties = product.GetType().GetProperties().ToList();
You can build list of properties using expression trees, e.g. you can make something like this:
var propertiesListBuilder = new PropertiesListBuilder<Product>();
propertiesListBuilder
.AddProperty(_ => _.Id)
.AddProperty(_ => _.Title);
var target = new TargetType();
target.Properties = propertiesListBuilder.Properties;
The only concern here is performance, i.e. it might be not good idea to recreate such property lists over and over again, most probably they should be cached. At the same time you'll get intellisense, compiler checks and refactoring support for your property lists.
Below is a sample implementation of this stuff.
static class PropertyInfoProvider<T>
{
public static PropertyInfo GetPropertyInfo<TProperty>(Expression<Func<T, TProperty>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
return (PropertyInfo)memberExpression.Member;
}
}
class PropertiesListBuilder<T>
{
public IEnumerable<PropertyInfo> Properties
{
get
{
return this.properties;
}
}
public PropertiesListBuilder<T> AddProperty<TProperty>(
Expression<Func<T, TProperty>> expression)
{
var info = PropertyInfoProvider<T>.GetPropertyInfo(expression);
this.properties.Add(info);
return this;
}
private List<PropertyInfo> properties = new List<PropertyInfo>();
}
typeof(Product).GetProperties() would give you all (public) properties as PropertyInfo[].
See also MSDN.

DataBinding: 'System.Web.UI.Pair' does not contain a property with the name 'First'

I'm data binding a list of pairs to a drop down list, for some reason it's not working and I'm intrigued.
The code I am using is :
public void BindDropDown(List<Pair> dataList)
{
ddlGraphType.DataTextField = "First";
ddlGraphType.DataValueField = "Second";
ddlGraphType.DataSource = dataList;
ddlGraphType.DataBind();
}
I'm getting this exception, which is a lie!
DataBinding: 'System.Web.UI.Pair' does not contain a property with the name 'First'.
Thanks in advance.
Added
I know what the exception means, but a pair object does contain the First and Second properties, that's where the problem lies.
First and Second are Fields not properties of Pair type. You need to create a class with two properties:
public class NewPair
{
public string First { get; set; }
public string Second { get; set; }
}
EDIT: Use of Tuple : suggested by #Damien_The_Unbeliever & #Chris Chilvers
List<Tuple<string, string>> list = new List<Tuple<string, string>>()
{
new Tuple<string,string>("One","1"),
new Tuple<string,string>("Two","2"),
};
ddlGraphType.DataTextField = "Item1";
ddlGraphType.DataValueField = "Item2";
ddlGraphType.DataSource = list;
ddlGraphType.DataBind();
Theat means the target property must be a dependency property. This also means that you cannot bind a field and Pair.First is field not property
public sealed class Pair
{
}
Fields:
Public field First Gets or sets the first object of the object pair.
Public field Second Gets or sets the second object of the object pair.
See MSDN.
Probably you've forgot the {get; set;} after declaring the properties.
public class A
{
//This is not a property
public string Str;
//This is a property
public string Str2 {get; set;}
}

Categories