23
votes

I need to set the properties of a class using reflection.

I have a Dictionary<string,string> with property names and string values.

Inside a reflection loop, I need to convert the string value to the appropriate property type while setting the value for each property. Some of these property types are nullable types.

  1. How can I know from PropertyInfo if the property is a nullable type?
  2. How can I set a nullable type using reflection?

edit: The first method defined in the comments on this blog seems to do the trick as well: http://weblogs.asp.net/pjohnson/archive/2006/02/07/437631.aspx

8
I doubt you mean "cast" - you can't "cast" a string to an integer, for example. See my answer re conversion.Marc Gravell
you are right - I meant convert - fixed the text!dotnetcoder

8 Answers

15
votes

Why do you need to know if it is nullable? And do you mean "reference-type", or "Nullable<T>"?

Either way, with string values, the easiest option would be via the TypeConverter, which is more-easily (and more accurately) available on PropertyDescriptor:

PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
// then per property...
PropertyDescriptor prop = props[propName];
prop.SetValue(obj, prop.Converter.ConvertFromInvariantString(value));

This should use the correct converter, even if set per-property (rather than per-type). Finally, if you are doing lots of this, this allows for acceleration via HyperDescriptor, without changing the code (other than to enable it for the type, done once only).

15
votes
  1. One way to do this is:

    type.GetGenericTypeDefinition() == typeof(Nullable<>)
    
  2. Just set is as per any other reflection code:

    propertyInfo.SetValue(yourObject, yourValue);
    
11
votes

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);
}
3
votes

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);
            }
        }

    }
}
1
votes

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");
            }
            ...
        }
...
}
1
votes

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);
    }
0
votes

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)

0
votes

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);

    }