I need to set the properties of a class using reflection.
I have a Dictionary
with property names and string values.
Inside
I've created small sample. If you have any questions regarding this code, please add comments.
EDIT: updated sample based on great comment by Marc Gravell
class Program
{
public int? NullableProperty { get; set; }
static void Main(string[] args)
{
var value = "123";
var program = new Program();
var property = typeof(Program).GetProperty("NullableProperty");
var propertyDescriptors = TypeDescriptor.GetProperties(typeof(Program));
var propertyDescriptor = propertyDescriptors.Find("NullableProperty", false);
var underlyingType =
Nullable.GetUnderlyingType(propertyDescriptor.PropertyType);
if (underlyingType != null)
{
var converter = propertyDescriptor.Converter;
if (converter != null && converter.CanConvertFrom(typeof(string)))
{
var convertedValue = converter.ConvertFrom(value);
property.SetValue(program, convertedValue, null);
Console.WriteLine(program.NullableProperty);
}
}
}
}
here is the safest solution for "nullable" object type
if (reader[rName] != DBNull.Value)
{
PropertyInfo pi = (PropertyInfo)d[rName.ToLower()];
if (pi.PropertyType.FullName.ToLower().Contains("nullable"))
pi.SetValue(item, reader[rName]);
else
pi.SetValue(item, Convert.ChangeType(reader[rName], Type.GetType(pi.PropertyType.FullName)), null);
}
Originally, the best solution is mentioned at MSDN forum. However, when you need to implement dynamic solution, where you don't know exactly how many nullable fields may be declared on a class, you best bet is to check if Nullable<> type can be assigned to the property, which you inspect via reflection
protected T initializeMe<T>(T entity, Value value)
{
Type eType = entity.GetType();
foreach (PropertyInfo pi in eType.GetProperties())
{
//get and nsame of the column in DataRow
Type valueType = pi.GetType();
if (value != System.DBNull.Value )
{
pi.SetValue(entity, value, null);
}
else if (valueType.IsGenericType && typeof(Nullable<>).IsAssignableFrom(valueType)) //checking if nullable can be assigned to proptety
{
pi.SetValue(entity, null, null);
}
else
{
System.Diagnostics.Trace.WriteLine("something here");
}
...
}
...
}
Checking for Nullable types is easy, int? is actually System.Nullable<System.Int32>
.
So you just check if the type is a generic instance of System.Nullable<T>
.
Setting shouldn't make a difference, nullableProperty.SetValue(instance, null)
or nullableProperty.SetValue(instance, 3)
I have used the following solution, avoiding use of type converter for taking more control over code.
I wrote a helper class for supporting operations
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
public static class ObjectExtensions
{
/// <summary>
/// Enable using reflection for setting property value
/// on every object giving property name and value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target"></param>
/// <param name="propertyName"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool SetProperty<T>(this T target, string propertyName, object value)
{
PropertyInfo pi = target.GetType().GetProperty(propertyName);
if (pi == null)
{
Debug.Assert(false);
return false;
}
try
{
// Convert the value to set to the properly type
value = ConvertValue(pi.PropertyType, value);
// Set the value with the correct type
pi.SetValue(target, value, null);
}
catch (Exception ex)
{
Debug.Assert(false);
return false;
}
return true;
}
private static object ConvertValue(Type propertyType, object value)
{
// Check each type You need to handle
// In this way You have control on conversion operation, before assigning value
if (propertyType == typeof(int) ||
propertyType == typeof(int?))
{
int intValue;
if (int.TryParse(value.ToString(), out intValue))
value = intValue;
}
else if (propertyType == typeof(byte) ||
propertyType == typeof(byte?))
{
byte byteValue;
if (byte.TryParse(value.ToString(), out byteValue))
value = byteValue;
}
else if (propertyType == typeof(string))
{
value = value.ToString();
}
else
{
// Extend Your own handled types
Debug.Assert(false);
}
return value;
}
}
Note: When You set a nullable value (eg. int?, the value need to be almost an integer or castable type. You cannot set int on a byte?. So, You need to convert properly. See ConvertValue() code, that checks either for type (int) and corresponding nullable type (int?) )
This is code for setting values with the required data structure, Dictionary.
public class Entity
{
public string Name { get; set; }
public byte? Value { get; set; }
}
static void SetNullableWithReflection()
{
// Build array as requested
Dictionary<string, string> props = new Dictionary<string, string>();
props.Add("Name", "First name");
props.Add("Value", "1");
// The entity
Entity entity = new Entity();
// For each property to assign with a value
foreach (var item in props)
entity.SetProperty(item.Key, item.Value);
// Check result
Debug.Assert(entity.Name == "First name");
Debug.Assert(entity.Value == 1);
}
Specifically to convert an integer to an enum and assign to a nullable enum property:
int value = 4;
if(propertyInfo.PropertyType.IsGenericType
&& Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null
&& Nullable.GetUnderlyingType(propertyInfo.PropertyType).IsEnum)
{
var enumType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
var enumValue = Enum.ToObject(enumType, value);
propertyInfo.SetValue(item, enumValue, null);
//-- suggest by valamas
//propertyInfo.SetValue(item, (value == null ? null : enumValue), null);
}