Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Gán giá trị property bằng Reflection trong C#

Reflection cho phép kiểm tra và thao tác metadata/type tại runtime. Namespace System.Reflection.

Các bước thiết lập giá trị property

  1. Lấy Type của object (obj.GetType() hoặc typeof(T)).
  2. Lấy PropertyInfo qua type.GetProperty("PropertyName").
  3. Gọi propertyInfo.SetValue(obj, value, index).

Ví dụ cơ bản


using System;
using System.Reflection;

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

class Program
{
    static void Main()
    {
        User user = new User { Id = 1, Name = "Old", Email = "old@test.com" };
        
        // Lấy PropertyInfo
        Type type = user.GetType();
        PropertyInfo nameProp = type.GetProperty("Name");
        PropertyInfo emailProp = type.GetProperty("Email");
        
        // Set value
        nameProp.SetValue(user, "New Name");
        emailProp.SetValue(user, "new@test.com");
        
        Console.WriteLine($"{user.Id}: {user.Name} - {user.Email}");
        // Output: 1: New Name - new@test.com
        
        Console.ReadLine();
    }
}

Các overload của SetValue


// 1. Basic
propertyInfo.SetValue(obj, value);

// 2. Với index (cho indexed property / indexer)
propertyInfo.SetValue(obj, value, new object[] { index });

// 3. Với BindingFlags (private, non-public)
PropertyInfo privateProp = type.GetProperty("InternalId", BindingFlags.NonPublic | BindingFlags.Instance);
privateProp.SetValue(obj, 999);

Generic Helper Method


public static class ReflectionHelper
{
    public static void SetPropertyValue<T>(T obj, string propertyName, object value)
    {
        if (obj == null) throw new ArgumentNullException(nameof(obj));
        if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(nameof(propertyName));
        
        PropertyInfo prop = typeof(T).GetProperty(propertyName, 
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            
        if (prop == null)
            throw new ArgumentException($"Property '{propertyName}' not found on {typeof(T).Name}");
        
        if (!prop.CanWrite)
            throw new InvalidOperationException($"Property '{propertyName}' is read-only");
        
        // Convert type nếu cần (string -> int, etc.)
        object convertedValue = Convert.ChangeType(value, prop.PropertyType);
        prop.SetValue(obj, convertedValue);
    }
}

// Usage
var user = new User();
ReflectionHelper.SetPropertyValue(user, "Name", "John");
ReflectionHelper.SetPropertyValue(user, "Age", "25"); // Auto convert string->int

Performance: Compile Delegate (Fast)


// Cache delegate cho performance cao (ví dụ: mapping hàng loạt)
public static class FastPropertySetter
{
    private static readonly Dictionary> _setters = new();
    
    public static void SetValue(object obj, string propertyName, object value)
    {
        string key = $"{obj.GetType().FullName}.{propertyName}";
        
        if (!_setters.TryGetValue(key, out var setter))
        {
            var prop = obj.GetType().GetProperty(propertyName);
            var instance = Expression.Parameter(typeof(object), "instance");
            var val = Expression.Parameter(typeof(object), "value");
            var castInstance = Expression.Convert(instance, obj.GetType());
            var castValue = Expression.Convert(val, prop.PropertyType);
            var call = Expression.Call(castInstance, prop.GetSetMethod(), castValue);
            setter = Expression.Lambda>(call, instance, val).Compile();
            _setters[key] = setter;
        }
        setter(obj, value);
    }
}

Lưu ý

  • Reflection chậm hơn direct call ~100-1000x. Cache delegate nếu gọi nhiều.
  • BindingFlags.NonPublic | BindingFlags.Instance để set private/internal property.
  • Property phải có set accessor (CanWrite == true).
  • Convert.ChangeType không hoạt động với nullable type (int?) -> cần handle riêng.