Using reflection in C# to modify fields in a loop, extension method - c#

After retrieving data from a database I find myself doing this to create a domain object from the data in a DataRow (in this case, a DVD):
DataRow drDvd = myDataTable.Rows[0];
Dvd myDvd = new Dvd();
myDvd.id = drDvd.Field<long>("id");
myDvd.title = drDvd.Field<string>("title");
myDvd.description = drDvd.Field<string>("description");
myDvd.releaseDate = drDvd.Field<DateTime>("releaseDate");
As I soon felt of course, I am doing this over and over in pseudo-code:
myDvd.field = drDvd.Field<field.type>(field.name);
And I wondered if I could get it into a loop, however I've never used reflection before. The code I tried is this:
Dvd aDvd = new Dvd();
Type t = aDvd.GetType();
FieldInfo[] fields = t.GetFields();
foreach (FieldInfo fi in fields)
{
fi.SetValue(aDvd, drDvd.Field<fi.FieldType>(fi.Name));
}
The problem is, as you may know, that the extension for the Field method of class DataRow does not accept a variable and needs to be explicitely filled in.
I am not that experienced in C# so I would like to pose the following two questions:
Is it good practice what I am trying to do?
How can I fill in the correct extension for Field<extension>(name)?

You'll need to get the method info for the generic method, and call invoke on it. This way you can pass in the generic type to it programmatically. I'm on my phone, but it should look something like this:
MethodInfo mField = typeof(Dvd).GetMethod("Field");
MethodInfo genericMethod = mField.MakeGenericMethod(new Type[] { fi.FieldType });
GenericMethod.Invoke(aDvd,new Object[]{fi.Name});

It is usually a bad practice to use reflection when it is not really necessary. Because reflection methods are checked at runtime rather than compile time, faulty code is harder to track, because the compiler can't check for errors.
If I were you, id have a look at the Entity Framework, because youre basically mapping database data to domain objects. http://msdn.microsoft.com/en-us/library/aa697427%28v=vs.80%29.aspx

This is one of the way of constructing and populating your domain object
DataRow drDvd = new DataRow();
Dvd aDvd = new Dvd();
Type type = typeof(Dvd);
foreach (FieldInfo fi in type.GetFields())
{
fi.SetValue(aDvd, drDvd[fi.Name]);
}
Your approach of using DataRow.Field may be round about. In you case, it is not applicable.
Alternatively you can think about using one of the Entity frameworks (NHibernate, Microsoft EF etc) in your application.

I would do a custom attribute. In doing an attribute you are stuck with your field name being the same as the database. I currently use this in my current applications and it works great. It is very similar to Entity SQL.
public class SqlMetaAttribute : Attribute
{
public string ColumnName { get; set; }
}
Then you have your class like this
public class Person
{
[SqlMeta(ColumnName = "First_Name")]
publice string FirstName { get; set; }
[SqlMeta(ColumnName = "Last_Name")]
publice string LastName { get; set; }
}
You would then have a helper class with the same kind of functions. In this case I am assuming the outside caller is looping through the datatable. Making it generic using the template T makes this really reusable. Rather than just having a "DVD" type implementation and coping and pasting for another.
public static T CreateObjectFromRow<T>(DataRow row)
{
var newObject = new T();
if (row != null) SetAllProperties(row, newObject);
return newObject;
}
public static void SetAllProperties<T>(DataRow row, T newObject)
{
var properties = typeof(T).GetProperties();
foreach(var propertyInfo in properties)
{
SetPropertyValue(row, newObject, propertyInfo);
}
}
public static void SetPropertyValue(DataRow row, T newObject, PropertyInfo propertyInfo)
{
var columnAttribute = propertyInfo.FindAttribute<SqlMetaAttribute>();
if (columnAttribute == null) return;
// If the row type is different than the object type and exception will be thrown, but that is
// okay because if that happens you have to fix your object you are using, or might need some
// more custom code to help you with that.
propertyInfo.SetValue(newObject, row.GetValue<object>(columnAttribute.ColumnName), null);
}
// Extension method for row.GetValue<object> used above
public static T GetValue<T>(this DataRow row, string columnName)
{
if (row.ColumnNameNotFound(columnName) || row.Table.Columns[columnName] == null || row[columnName] is DBNull)
{
return default(T);
}
return (T)row[columnName];
}

Related

Can I access a class variable with another variable?

i want to do a class constructor that takes a dicionary as parameter and initialize all the class variables that are listed as key in the dictionary, after of course a type conversion:
public class User
{
public int id;
public string username;
public string password;
public string email;
public int mana_fire;
public int mana_water;
public int mana_earth;
public int mana_life;
public int mana_death;
public User ()
{
}
public User(Dictionary<string,string> dataArray){
FieldInfo[] classVariablesInfoList = typeof(User).GetFields();
for(int i = 0; i < classVariablesInfoList.Length; i++)
{
if(dataArray.ContainsKey(classVariablesInfoList[i].Name)){
//missing code here :)
//need something like classVariable= dataArray["classVariablesInfolist[i].name"]; ?
}
}
}
}
but i can't find out how to do this!
Can you please help? :)
You can use the SetValue frunction from reflection:
FieldInfo f = classVariablesInfoList[i];
if (f.ReflectedType == typeof(int))
{
f.SetValue(this, Convert.ToInt32(dataArray[f.Name]));
}
else
{
f.SetValue(this, dataArray[classVariablesInfoList[i].Name]);
}
But it is a really uncommon way to do this with a dictionary. You should considder accessing the fields directly or add parameters to the constructor for any field. And fields should never be public - use properties instead.
The following will work if Convert.ChangeType() is able to handle the conversion. There are a lot of problems waiting to occur, for example handling numbers or dates where the string representation depends on the locale. I would really suggest to use usual typed constructor parameters or standard (de)serialization mechanism if possible. Or at least use a dictionary containing objects instead of strings to get rid of the conversion, again if possible.
public User(Dictionary<String, String> data)
{
var fields = typeof(User).GetFields();
foreach (field in fields)
{
if (data.ContainsKey(field.Name))
{
var value = Convert.ChangeType(data[field.Name], field.MemberType);
field.SetValue(this, value);
}
}
}
I would like to separate your problem into two parts.
1. Applying conversion
The FieldInfo type present a FieldType property that is the actual type of the field, using this Type we can use the non-generic ChangeType method of System.Convert, this method will be able convert some types to others. Luckily it support String to Int.
Usage:
Convert.ChangeType(OLD_VALUE, TARGET_TYPE);
2. Setting the field
The field info class has a SetValue method (FieldInfo.SetValue), it has two parameters, the first one is the current (ie. this) instance (or the instance you wish to change). the second is the new value you wish to set.
Putting it all together
[...]
var fieldInfo = classVariablesInfoList[i];
var name = fieldInfo.Name;
var targetType = fieldInfo.Type;
var value = Convert.ChangeType(dataArray[name], targetType);
classVariablesInfoList[i].SetValue(this, value);

Possible to create list from class of public strings in c#

I am using C# to create an app and want to be able to easily do a foreach() loop through my 120 strings that i have. All these strings are constructed as below:
public class _currentweekapi
{
public string _1_artist { get; set; }
public string _2_artist { get; set; }
public string _3_artist { get; set; }
...
}
Is it possible to be able to do a foreach() loop to loop through all the values. The strings are set automatically from a JSON feed so i cannot get the values straight into a list.
I have read up on the topic and understand there are ways through 'Reflection' but I am new to C# and reading up on it doesn't make any sense to me.
Could someone please help me with a solution to this stating exactly how I could implement this?
Using reflection is quite easy - try this for a start
var properties = typeof(_currentweekapi).GetProperties();
foreach(PropertyInfo info in properties) {
if (info.Name.EndsWith("artist") {
// do something amazing
}
}
see - http://msdn.microsoft.com/en-us/library/kyaxdd3x.aspx for more information
The first thing you should be trying to do, is NOT store the strings in explicitly named properties, as your class definition appears to be doing. The most appropriate scenario would seem to be a List<string> type property that can hold them all in your class. This would also mean you already have your list ready to go.So, if you are able to, change the class, or use another, which accepts the JSON feed, and uses .Add() on a property of type List<string>, rather than explicitly setting 120 properties.
Like this:
public class ArtistData
{
public List<string> Artists{ get; set; }
public ArtistData()
{
this.Artists = new List<string>(0);
}
public void PopulateArtists(string jsonFeed)
{
// here something desrializes your JSON, allowing you to extract the artists...
// here you would be populating the Artists property by adding them to the list in a loop...
}
}
Then, you have your list in the property Artists, and can use the list directly, or return it by doing:
string[] artists = myInstance.Artists.ToArray();
However, you seem to have indicated that you cannot change the fact that they end up served to you as individual properties in the class you showed us, so...
Assuming you have no choice, but to start from a class such as the one you showed, here is a way you could do a loop through all those values, just as you asked for, all that will be required is that you pass your class instance into one of the methods below as the one parameter that each requires:
// this will return a list of the values contained in the properties...
public List<string> GetListFromProperties<T>(T instance)
{
Type t = typeof(T);
PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
// As a simple list...
List<string> artists = new List<string>(props.Length);
for (int i = 0; i < props.Length; i++)
{
if(!props[i].Name.Contains("_artist")){ continue; }
artists.Add(props[i].GetValue(instance, null).ToString());
}
return artists;
}
// this will return a dictionary which has each artist stored
// under a key which is the name of the property the artist was in.
public Dictionary<string,string> GetDictionaryFromProperties<T>(T instance)
{
Type t = typeof(T);
PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
// As a dictionary...
Dictionary<string,string> artists = new Dictionary<string,string>(props.Length);
for (int i = 0; i < props.Length; i++)
{
if(artists.ContainsKey(props[i].Name) || !props[i].Name.Contains("_artist")){ continue; }
artists.Add(props[i].Name, props[i].GetValue(instance, null).ToString());
}
return artists;
}
Either one should help, but do not use the dictionary one, as it entails more overhead, unless you really need to know the name of the property each artist came from as well, in which case it is more helpful than a simple list.Incidentally, since the methods are generic, that is, they accept a parameter of type T, the same methods will work for ANY class instance, not just the one you are struggling with now. REMEMBER: although it may appear extremely convenient to you at the moment, this is not necessarily the best way to approach this. Better than this, would be the initial suggestions I made for re-working the class altogether so this sort of thing is not required.
You can do something like this:
List<string> list = new List<string>();
foreach (var property in _currentweekapi.GetType().GetProperties())
{
list.Add(property.GetValue(_currentweekapi, null));
}
If you can assure that there are no other types of properties then this will work:
var list =
typeof(_currentweekapi)
.GetProperties()
.Select(pi => (string)pi.GetValue(x, null))
.ToList();

Another c# reflection

I have a csv file where each row is a different type of record. I am parsing the csv and want to store the rows (varied types of records) in various types of custom classes.
At each row i need to instantiate a different class based on the record type.
So taken from other reflection examples, I have the code below;
Type type = Type.GetType("myNamespace." + className);
object recordclass = Activator.CreateInstance(type);
so i have an object named recordclass of the correct type, but how do I use it?
all I really want to do is access the properties of the class and populate the row data, and then later add to a container class.
I guess im missing something about the runtime nature of reflection. Please help me connect the dots!
Hope that all makes sense!
TIA,
Gary
With the example you give you could cast your object to the actual type you need:
Type type = Type.GetType("myNamespace." + className);
object recordclass = Activator.CreateInstance(type);
var record = recordClass as ConcreteRecordType;
if(record != null)
record.Name = csv["Name"];
Alternatively look into using a Factory to return populated record objects:
public class RecordFactory
{
RecordBase ParseCsvRow(string[] columns)
{
const int typeDescriminatorColumn = 0;
switch (columns[typeDescriminatorColumn])
{
case "RecordTypeA":
return new RecordTypeA(columns[1], columns[2], ...);
case "RecordTypeB":
return new RecordTypeB(columns[1], columns[2], ...);
default:
throw new InvalidOperationException("Unexpected descriminator: " + columns[typeDescriminatorColumn]);
}
}
}
If you want to store values into your recordclass's property via reflection use this
var property = type.GetProperty(propertyName);
property.SetValue(recordclass,value,null);
If you read the docs, you will see, Type.GetType requires a full qualified type name.
If I understand the problem correctly you have a file which contains text values for records. Each record is stored in a single line, and the start of each line is an identifier to say which kind of record is to be built.
It is possible to use reflection for this but not really neccessary. The problem with using reflection is that you need to know all the properties for the different record types in order to access them by name. At this point, you may as well be working with typed objects but if you are using the a single routine to create all the records (using CreateInstance()) all you have is an untyped object.
Another solution is to have a set of routines each of which take in, say an IEnumerable (the input line split by the comma, excluding the record id) and return an object (or a record, if you have a base record class) and use a factory to select which routine to use for each row.
You register the routines with the factory by some ID (the first field in the record as you are doing is good, it can be the record class name but doesn't have to be) and the iterate through the CSV lines, using the first piece to select the method from the factory and building the record.
Hopefully the example will explain a bit better :? Sorry about the volume of code
The builders in the example just return empty records but populating them from the row pieces should be easy. Another version is to just pass in the row, or a set of rows if a record can cover a number of rows (a but more complicated if the records take in different numbers of rows)
hth,
Alan.
public string[] Input = new[]{
"R1, F1, F2, F3",
"R2, F2, F4",
"R3, F2",
"R3, F2",
"R4, F1, F2, F3, F4"
};
public class RecordOne {
}
public class RecordTwo {
}
public class RecordThree {
}
public class RecordFour {
}
public class BuilderFactory {
public BuilderFactory() {
Builders = new Dictionary<string, Func<IEnumerable<string>, object>>();
}
private Dictionary<string, Func<IEnumerable<string>, object>> Builders { get; set; }
public void RegisterBuilder(string name, Func<IEnumerable<string>, object> builder) {
Builders.Add(name, builder);
}
public Func<IEnumerable<string>, object> GetBuilder(string name) {
return Builders[name];
}
}
[TestMethod]
public void LoadRecords() {
var factory = new BuilderFactory();
factory.RegisterBuilder("R1", BuildRecordOne);
factory.RegisterBuilder("R2", BuildRecordTwo);
factory.RegisterBuilder("R3", BuildRecordThree);
factory.RegisterBuilder("R4", BuildRecordFour);
var output = Input.Select(line => {
var pieces = line.Split(',').Select(val => val.Trim());
var builder = factory.GetBuilder(pieces.First());
return builder(pieces.Skip(1));
});
Assert.IsTrue(new[] {typeof(RecordOne),
typeof(RecordTwo),
typeof(RecordThree),
typeof(RecordThree),
typeof(RecordFour)}.SequenceEqual(output.Select(rec => rec.GetType())));
}
private static RecordOne BuildRecordOne(IEnumerable<string> pieces) {
return new RecordOne();
}
private static RecordTwo BuildRecordTwo(IEnumerable<string> pieces) {
return new RecordTwo();
}
private static RecordThree BuildRecordThree(IEnumerable<string> pieces) {
return new RecordThree();
}
private static RecordFour BuildRecordFour(IEnumerable<string> pieces) {
return new RecordFour();
}

How can I use reflection to find the properties which implement a specific interface?

Consider this example:
public interface IAnimal
{
}
public class Cat: IAnimal
{
}
public class DoStuff
{
private Object catList = new List<Cat>();
public void Go()
{
// I want to do this, but using reflection instead:
if (catList is IEnumerable<IAnimal>)
MessageBox.Show("animal list found");
// now to try and do the above using reflection...
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
//... what do I do here?
// if (*something*)
MessageBox.Show("animal list found");
}
}
}
Can you complete the if statement, replacing something with the correct code?
EDIT:
I noticed that I should have used a property instead of a field for this to work, so it should be:
public Object catList
{
get
{
return new List<Cat>();
}
}
You can look at the properties' PropertyType, then use IsAssignableFrom, which I assume is what you want:
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (typeof(IEnumerable<IAnimal>).IsAssignableFrom(property.PropertyType))
{
// Found a property that is an IEnumerable<IAnimal>
}
}
Of course, you need to add a property to your class if you want the above code to work ;-)
Please note that in your example, catList would not be found with GetType().GetProperties (). You would use GetType().GetFields () instead.
If you are trying to determine whether the property is defined as IEnumerable you can do this:
if (typeof(IEnumerable<IAnimal>) == property.PropertyType)
{
MessageBox.Show("animal list found");
}
If you want to know if you can assign the value of the property into a IEnumerable<IAnimal>, do this:
if (typeof(IEnumerable<IAnimal>).IsAssignableFrom (property.PropertyType))
{
MessageBox.Show("animal list found");
}
If the property type is not specific enough (like object Animal{get;set;}) to get your answer, you will need to get the value to decide. You can do this:
object value = property.GetValue(this, null);
if (value is IEnumerable<IAnimal>)
{
MessageBox.Show("animal list found");
}
Another way to do it is just call GetProperties() on the interface from within the object, opposed to on the object itself.
public static void DisplayObjectInterface(object source, Type InterfaceName)
{
// Get the interface we are interested in
var Interface = source.GetType().GetInterface(InterfaceName.Name);
if (Interface != null)
{
// Get the properties from the interface, instead of our source.
var propertyList = Interface.GetProperties();
foreach (var property in propertyList)
Debug.Log(InterfaceName.Name + " : " + property.Name + "Value " + property.GetValue(source, null));
}
else
Debug.Log("Warning: Interface does not belong to object.");
}
I like to make InterfaceName parameter a Type to avoid any typo errors when looking up GetInterface() by string name.
Usage:
DisplayObjectInterface(Obj, typeof(InterFaceNameGoesHere));
EDIT: I just noticed that your example was a collection, this will not work on a collection passed as a whole. You would have to pass each item individually. I'm tempted to delete but this will probably help others that google this same question looking for a non-collection solution.

Mapping IDataReader to object without third party libraries

Are there any quick way to map some IDataReader to object without third party libraries such as AutoMapper or ValueInjecter?
I'm not sure what you mean by quick, but you can put something together using reflection. There's a lot of assumptions you'll have to make, such as all your object's values are set via properties. And, your DataReader columns MUST match your object property name. But you could do something like this:
NOTE: The SetProperty function is from an article on DevX. (It was in VB.NET, and I converted it to C# -- if there are mistakes, I probably missed something.)
IList<MyObject> myObjList = new List<MyObject>();
while (reader.Read()){
int fieldCount = reader.FieldCount;
MyObject myObj = new MyObject();
for(int i=0;i<fieldCount;i++){
SetProperty(myObj, reader.GetName(i), reader.GetOrdinal(i));
}
myObjList.Add(myObj);
}
bool SetProperty(object obj, String propertyName, object val) {
try {
//get a reference to the PropertyInfo, exit if no property with that
//name
System.Reflection.PropertyInfo pi = obj.GetType().GetProperty(propertyName);
if (pi == null) then return false;
//convert the value to the expected type
val = Convert.ChangeType(val, pi.PropertyType);
//attempt the assignment
pi.SetValue(obj, val, null);
return true;
}
catch {
return false;
}
}
No guarantees that this code will run (I'm just typing it and not compiling/testing it), but it at least may be a start.
I've done things like this in the past and have gotten fancy with applying attributes to my properties stating what data reader column to map to the property. Too much to include here, but this is just a starter and hopefully is what you're looking for.
Hope this helps!
Sure,
class MyObject
{
public string SomeProperty { get; set; }
public MyObject(IDataReader reader)
{
SomeProperty = reader.GetString(0);
// - or -
SomeProperty = reader.GetString(reader.GetOrdinal("SomeProperty"));
// etc.
}
}

Categories