首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 3.0 模擬突變賦值:以擴充方法實現物件屬性批量指派

     編輯:關於C#

「突變賦值」概念源自動態語言,允許以單一運算式將多個屬性值批量指派給物件。C# 3.0 雖無原生語法支援,但結合匿名類型與擴充方法,可透過反射實現相近功能。

概念對比

傳統逐一指派


form.Text = "Hello World";
form.Top = 100;
form.Left = 200;

理想突變賦值語法(假想)


form := new { Text = "Hello World", Top = 100, Left = 200 };

C# 3.0 反射實作


using System.Reflection;

public static class MutantExtensions
{
    /// 
    /// 將來源匿名物件的屬性值複製到目標物件同名同型別屬性
    /// 
    public static void MAssign(this object target, object source)
    {
        if (target == null || source == null) return;
        
        Type targetType = target.GetType();
        Type sourceType = source.GetType();
        
        foreach (PropertyInfo srcProp in sourceType.GetProperties())
        {
            if (!srcProp.CanRead) continue;
            
            PropertyInfo targetProp = targetType.GetProperty(srcProp.Name, srcProp.PropertyType);
            if (targetProp == null || !targetProp.CanWrite) continue;
            
            object value = srcProp.GetValue(source, null);
            targetProp.SetValue(target, value, null);
        }
    }
}

使用範例


// WinForms 表單
Form form = new Form();
form.MAssign(new { Text = "Hello World", Top = 100, Left = 200 });

// 任意 POCO 物件
class Person { public string Name { get; set; } public int Age { get; set; } }
Person p = new Person();
p.MAssign(new { Name = "Tom", Age = 25 });

進階增強版本


public static class MutantExtensions
{
    public static void MAssign(this object target, object source, bool ignoreCase = false)
    {
        if (target == null || source == null) return;
        
        var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
        var targetProps = target.GetType().GetProperties(bindingFlags)
            .ToDictionary(p => ignoreCase ? p.Name.ToLower() : p.Name, p => p);
        
        foreach (var srcProp in source.GetType().GetProperties(bindingFlags))
        {
            if (!srcProp.CanRead) continue;
            
            string key = ignoreCase ? srcProp.Name.ToLower() : srcProp.Name;
            if (!targetProps.TryGetValue(key, out var targetProp)) continue;
            if (!targetProp.CanWrite) continue;
            if (targetProp.PropertyType != srcProp.PropertyType) continue; // 嚴格型別匹配
            
            targetProp.SetValue(target, srcProp.GetValue(srcProp, null));
        }
    }
    
    // 僅複製指定屬性
    public static void MAssign(this object target, object source, params string[] propertyNames)
    {
        var props = source.GetType().GetProperties()
            .Where(p => propertyNames.Contains(p.Name, StringComparer.OrdinalIgnoreCase));
        
        foreach (var srcProp in props)
        {
            var targetProp = target.GetType().GetProperty(srcProp.Name, srcProp.PropertyType);
            if (targetProp?.CanWrite == true && srcProp.CanRead)
                targetProp.SetValue(target, srcProp.GetValue(source));
        }
    }
}

效能與限制考量

  • 反射開銷:每次呼叫需列舉屬性、查找目標屬性,頻繁呼叫建議快取 PropertyInfo 或使用表達式樹編譯委派
  • 型別安全:僅匹配名稱與型別完全相同的屬性,避免隱式轉換錯誤
  • 唯讀屬性:目標屬性需可寫入(CanWrite),init 唯讀屬性(C# 9+)在建構後不可設定
  • 結構體:傳入結構體會對複本操作,需用 ref 擴充方法(C# 7.2+ ref struct 不支援擴充)

現代替代方案

  • AutoMapper:專業物件對物件映射庫,支援慣例配置、扁平化、投影
  • C# 9+ record + with 運算式:var newPerson = person with { Name = "New" }; 建立新實例而非突變
  • 原生物件初始化器:新建物件時首選 new Person { Name = "Tom", Age = 25 }

此反射擴充方法適合舊版專案維護、動態物件更新、測試資料構建等場景,新專案建議採用不可變模式與專業映射庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved