Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

วิธีตั้งค่า Property ผ่าน Reflection ใน C#

Reflection ใน .NET ใช้ Namespace System.Reflection เพื่อตรวจสอบและจัดการ Metadata ของ Types, Properties, Methods, Fields ใน Runtime

ขั้นตอนการ Set Property ผ่าน Reflection

  1. ดึง Type ของ Object (obj.GetType() หรือ typeof(T))
  2. หา PropertyInfo ด้วย GetProperty(name)
  3. เรียก SetValue(obj, value, index)

ตัวอย่างพื้นฐาน

using System;
using System.Reflection;

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

static void Main()
{
    User user = new User { Id = 1, Name = "John", Email = "john@example.com" };
    
    // 1. Get Type
    Type type = user.GetType();
    
    // 2. Get PropertyInfo (Public Instance Properties)
    PropertyInfo nameProp = type.GetProperty("Name");
    PropertyInfo emailProp = type.GetProperty("Email");
    
    if (nameProp != null && nameProp.CanWrite)
    {
        // 3. Set Value
        nameProp.SetValue(user, "Jane");
        Console.WriteLine($"Name changed to: {user.Name}"); // Jane
    }
    
    if (emailProp != null && emailProp.CanWrite)
    {
        emailProp.SetValue(user, "jane@example.com");
        Console.WriteLine($"Email changed to: {user.Email}");
    }
    
    // Set โดยใช้ Object (Boxing สำหรับ Value Types)
    PropertyInfo idProp = type.GetProperty("Id");
    idProp?.SetValue(user, 999);
    Console.WriteLine($"Id changed to: {user.Id}"); // 999
}

ตัวอย่างขั้นสูง: Dynamic Object Mapper

public static class ObjectMapper
{
    public static void MapProperties(object source, object target, bool ignoreCase = true)
    {
        var sourceProps = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var targetProps = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        
        var comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
        
        foreach (var srcProp in sourceProps)
        {
            if (!srcProp.CanRead) continue;
            
            var tgtProp = Array.Find(targetProps, p => 
                p.Name.Equals(srcProp.Name, comparison) && p.CanWrite && 
                p.PropertyType.IsAssignableFrom(srcProp.PropertyType));
            
            if (tgtProp != null)
            {
                object value = srcProp.GetValue(source);
                tgtProp.SetValue(target, value);
            }
        }
    }
}

// ใช้งาน: Copy Properties ระหว่าง Object แบบ Dynamic
var dto = new UserDto { Id = 1, Name = "John", Email = "john@test.com", Extra = "Ignore" };
var entity = new UserEntity();
ObjectMapper.MapProperties(dto, entity); // Copy Id, Name, Email

BindingFlags สำคัญ

Flag ความหมาย
Public Public Members
NonPublic Private/Protected/Internal Members
Instance Instance Members (Non-static)
Static Static Members
DeclaredOnly เฉพาะที่ประกาศใน Type นี้ (ไม่รวม Base Class)
// หา Private Property
PropertyInfo privateProp = type.GetProperty("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
privateProp?.SetValue(obj, "New Secret", null); // index = null สำหรับ Non-indexed Property

Indexed Properties (Indexers)

public class Sample
{
    public string this[int index] { get; set; }
}

var sample = new Sample();
PropertyInfo indexer = typeof(Sample).GetProperty("Item"); // Default Name = "Item"
indexer.SetValue(sample, "Value", new object[] { 0 }); // Index = 0
Console.WriteLine(sample[0]); // "Value"

Performance Considerations

  • Reflection ช้ากว่า Direct Access 10-100x
  • Cache PropertyInfo ไว้ใช้ซ้ำ (Static Dictionary<Type, PropertyInfo[]>)
  • ใช้ Delegate.CreateDelegate หรือ Expression Trees สร้าง Fast Setter
  • Libraries: FastMember, Mapster, AutoMapper ใช้ Expression Trees เร็วกว่า

Fast Setter ด้วย Expression Tree (Cacheable)

public static class FastReflection
{
    private static readonly ConcurrentDictionary<(Type, string), Action> _setters = new();
    
    public static void SetPropertyFast(object obj, string propertyName, object value)
    {
        var key = (obj.GetType(), propertyName);
        var setter = _setters.GetOrAdd(key, CreateSetter);
        setter(obj, value);
    }
    
    private static Action CreateSetter((Type type, string name) key)
    {
        var prop = key.type.GetProperty(key.name, BindingFlags.Public | BindingFlags.Instance);
        if (prop == null || !prop.CanWrite) return (o, v) => { };
        
        var objParam = Expression.Parameter(typeof(object), "obj");
        var valParam = Expression.Parameter(typeof(object), "value");
        
        var castObj = Expression.Convert(objParam, key.type);
        var castVal = Expression.Convert(valParam, prop.PropertyType);
        var callSet = Expression.Call(castObj, prop.GetSetMethod());
        
        return Expression.Lambda>(callSet, objParam, valParam).Compile();
    }
}

ข้อควรระวัง

  • NullReferenceException หาก Property ไม่มี (GetProperty คืน null)
  • ArgumentException หาก Type ไม่ตรง (Value Type ต้อง Boxing)
  • TargetException หาก Object เป็น Null หรือ Type ไม่ตรง
  • Security: Partial Trust อาจจำกัด Reflection