Reputation: 379
I've got this helper method SetProperty that sets the property of an object via reflection. Below, is 2 scenarios where I use that method. The first method CreateInstance works just fine, but the second method Insert doesn't work.
In the 2nd method, the property set on the object is lost as soon as the method SetProperty returns. I've debugged it via visual studio. The object has the property set uptill the last closing curly braces. Then as control is returned to the caller Insert, the Property value is lost.
Method that sets properties
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetProperty(object destination, string propertyName, object value)
{
var type = destination.GetType();
var property = type.GetProperty(propertyName);
var convertedVal = Convert(value, property.PropertyType);
property.SetValue(destination, convertedVal);
}
The SetProperty method works fine in this method
public static T CreateInstance<T>(SqlDataReader row, IEnumerable<CLASS> columns)
{
var type = typeof(T);
var obj = Activator.CreateInstance(type, true);
foreach (var column in columns)
{
SetProperty(obj, column.BackingPropertyName, column.Name);
}
return (T)obj;
}
The SetProperty method doesn't work in this method
public T Insert<T>(T obj, string table = null)
{
// CODE CHUNK
using (var conn = new SqlConnection(this.ConnectionString))
{
conn.Open();
using (var cmd = new SqlCommand(query.ToString(), conn))
{
// CODE CHUNK
var autoGeneratedValue = cmd.ExecuteScalar();
if (temp.AutoGeneratedColumn != null)
{
ReflectionHelper.SetProperty(
obj,
temp.AutoGeneratedColumn.BackingPropertyName,
autoGeneratedValue
);
}
}
}
return obj;
}
EDIT - Add Console app to enable duplication
To replicate create new console app, then paste this code in Program.cs (Or equivalent)
using System;
using System.Runtime.CompilerServices;
namespace ConsoleApplication1
{
public struct Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public int Age { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
var p = new Person
{
ID = 93
};
var res = SetProperty<Person>(ref p, "Age", 34);
Console.WriteLine(p.Age);
Console.WriteLine(res.Age);
Console.Read();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T SetProperty<T>(ref T destination, string propertyName, object value)
{
var type = destination.GetType();
var property = type.GetProperty(propertyName);
var convertedVal = Convert(value, property.PropertyType);
property.SetValue(destination, convertedVal);
return (T)destination;
}
private static object Convert(object source, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
if (destinationType.IsGenericType &&
destinationType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (source == null)
{
return null;
}
destinationType = Nullable.GetUnderlyingType(destinationType);
}
return System.Convert.ChangeType(source, destinationType);
}
}
}
Upvotes: 1
Views: 128
Reputation: 109567
From your repro, I can see what the issue is.
This is being caused by your Person
struct being boxed.
Consider this line of code in the SetProperty<T>()
method:
property.SetValue(destination, convertedVal);
The first parameter of SetValue()
is of type object
. What that means is that when the struct destination
is passed to it, it is boxed and the property is set on the boxed copy - leaving the original one unchanged. (Note that boxing only affects value types such as structs - if Person
was a class, it wouldn't be boxed and it would work as expected.)
To fix this, you have to "manually" box the Person
struct by assigning it to an object
, then set the property on that object and finally assign it back to the original struct ref, like so:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T SetProperty<T>(ref T destination, string propertyName, object value)
{
var type = destination.GetType();
var property = type.GetProperty(propertyName);
var convertedVal = Convert(value, property.PropertyType);
object boxed = destination;
property.SetValue(boxed, convertedVal);
destination = (T) boxed;
return (T)boxed;
}
Upvotes: 2