【问题标题】:Weird issue with setting value via reflection通过反射设置值的奇怪问题
【发布时间】:2016-06-21 14:39:47
【问题描述】:

我有这个辅助方法SetProperty,它通过反射设置对象的属性。下面是我使用该方法的 2 个场景。第一种方法 CreateInstance 工作得很好,但第二种方法 Insert 不起作用。

在第二种方法中,对象上设置的属性会在 SetProperty 方法返回时丢失。我已经通过visual studio对其进行了调试。该对象具有设置到最后一个右花括号的属性。然后当控制权返回给调用者 Insert 时,Property 值将丢失。

设置属性的方法

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

SetProperty 方法在此方法中运行良好

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

SetProperty 方法在此方法中不起作用

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

编辑 - 添加控制台应用以启用复制

要复制创建新的控制台应用程序,然后将此代码粘贴到 Program.cs(或等效项)中

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

【问题讨论】:

  • 你能在一个简单的控制台应用程序中重现这个吗?
  • 也许你在第二个例子中的 T 是 struct 所以它作为值传递并且在 SetProperty 之后你有旧对象?
  • @MatthewWatson,“在控制台应用程序中重现”是什么意思。我把它作为一个类库,我也有一些单元测试可以让我遇到这种情况。
  • @ThatGuy 我的意思是其他人可以运行的最小且完整的复制,以便对其进行调查。
  • 使用 ref 关键字传递对象,它应该可以正常工作

标签: c# .net reflection


【解决方案1】:

从您的复制品中,我可以看出问题所在。

这是由您的 Person 结构被装箱引起的。

考虑SetProperty&lt;T&gt;()方法中的这行代码:

property.SetValue(destination, convertedVal);

SetValue() 的第一个参数是object 类型。这意味着当结构 destination 被传递给它时,它被装箱并且属性被设置在装箱的副本上 - 保持原始副本不变。 (请注意,装箱只影响值类型,例如结构 - 如果 Person 是一个类,它不会被装箱,它会按预期工作。)

要解决此问题,您必须通过将 Person 结构分配给 object 来“手动”将其装箱,然后设置该对象的属性,最后将其分配回原始结构引用,如下所示:

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

【讨论】:

  • 谢谢@Matthew,这就是秘密酱汁。整个装箱和拆箱有时会令人困惑。再次感谢。
猜你喜欢
  • 2010-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
相关资源
最近更新 更多