采用Delegate对一个未知类型的对象进行"遍历"

使用Delegate遍历未知类型对象
本文介绍了一种使用Delegate高效遍历未知类型对象的属性和字段的方法,并提供了一个具体的实现示例,包括定义接口、类及测试过程。

文章阅读顺序建议:
本系列有一个递进的顺序,可依次阅读以下的文章:
一、采用Delegate对一个未知类型的对象进行"遍历"
二、采用表达式树(Expression Tree)对一个对象的属性进行“遍历”
三、 采用表达式树(Expression Block)对一个对象的属性进行“遍历”



由于有时候需要对一个不知类型的object进行“遍历”,得到它的所有公有的属性和字段。虽然可以采用反射来实现,但是代价太高了,特别是要多次执行时。因此,我写了一个用Delegate来实现的方法,供大家参考和交流。

我定义了MemberAccessor.dll。里面有四个文件,具体的内容如下:

定义一个获得对象的某个Member的Value的接口:IMemberAccessor.cs

namespace MemberAccessor
{
    /// <summary>
    /// 获得Member值的接口
    /// </summary>
    public interface IMemberAccessor
    {
        object GetValue(object instance);
    }
}



定义一个获取指定类型的指定属性的GetValue的类: PropertyDelegateAccessor.cs
using System;
using System.Reflection;

namespace MemberAccessor
{
    /// <summary>
    /// 针对PropertyInfo,生成一个能够获得PropertyInfo Value的实例
    /// </summary>
    /// <typeparam name="TType"></typeparam>
    /// <typeparam name="TMember"></typeparam>
    public class PropertyDelegateAccesstor<TType, TMember> : IMemberAccessor
    {
        private Func<TType, TMember> m_getValueDelegate;

        public PropertyDelegateAccesstor(Type type, MethodInfo methodInfo)
        {
            m_getValueDelegate =
                (Func<TType, TMember>) Delegate.CreateDelegate(typeof (Func<TType, TMember>), null, methodInfo);
        } 

        public object GetValue(object instance)
        {
            return m_getValueDelegate((TType)instance);
        }
    }
}



定义一个获取指定类型的指定字段的GetValue的类:FieldDelegateAccessor
using System;
using System.Reflection;

namespace MemberAccessor
{
    /// <summary>
    /// 提交一个FieldInfo,生成一个获得该FieldInfo Value的实例
    /// </summary>
    public class FieldDelegateAccessor:IMemberAccessor
    {
        private Func<object, object> m_getValueDelegate;
 
        public FieldDelegateAccessor(FieldInfo fieldInfo)
        {
            m_getValueDelegate = fieldInfo.GetValue;
        }
        public object GetValue(object instance)
        {
            return m_getValueDelegate(instance);
        }
    }
}



利用上面的三个接口与类,生成一个针对指定类型的所有属性与字段访问的类:InstanceDelegateAccessor.cs
using System;
using System.Collections.Generic;
using System.Reflection;

namespace MemberAccessor
{
    /// <summary>
    /// 针对某一个类型的实例,生成一个获得该类型的相应Property与Field 值的实例
    /// </summary>
    public class InstanceDelegateAccessor
    {
        private Dictionary<string, IMemberAccessor> m_memberAccessors;

        public InstanceDelegateAccessor(Type type, BindingFlags propertyBindingFlags, BindingFlags fieldBindingFlags)
        {
            m_memberAccessors = new Dictionary<string, IMemberAccessor>();

            #region create accessor
            //property
            var pis = type.GetProperties(propertyBindingFlags);
            foreach (var pi in pis)
            {
                var name = pi.Name;
                var accessor =
                    Activator.CreateInstance(
                        typeof (PropertyDelegateAccesstor<,>).MakeGenericType(type, pi.PropertyType), type, pi.GetGetMethod()) as
                    IMemberAccessor;
                m_memberAccessors.Add(name, accessor);
            }
            //field
            var fis = type.GetFields(fieldBindingFlags);
            foreach (var fi in fis)
            {
                var name = fi.Name;
                var accessor = new FieldDelegateAccessor(fi);
                   
                m_memberAccessors.Add(name, accessor);
            }

            #endregion
        }
    
        public object GetValue(object instance, string memberName)
        {
            if (instance == null || string.IsNullOrWhiteSpace(memberName))
            {
                throw new ArgumentNullException("input paremeters have null value.");
            }
            IMemberAccessor accessor = null;
            if (m_memberAccessors.TryGetValue(memberName, out accessor) == false || accessor == null)
            {
                throw new ArgumentException("can not find any MemberDelegate for memberName:" + memberName);
            }

            return accessor.GetValue(instance);
        }
    }
      /// <summary>
        /// 返回一个类的所有字段,属性的序列对:
        /// Prop: Value
        /// Field: Value
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        public IList<Tuple<object,object>> GetValues(object instance)
        {
            var objs = new List<Tuple<object, object>>();
            foreach (var key in m_memberAccessors.Keys)
            {
                var value = GetValue(instance, key);
                objs.Add(new Tuple<object, object>(key, value));
            }
            return objs;
        }
}


最后,测试:
说明:
    在Main方法里先创建两个对象,一个是StringTest,另一个是IntTest。并创建与它们类型相对应的InstanceDelegateAccessor对象。
最后分别调用ShowAllPublicValue,将对象和它对应的InstanceDelegateAccessor对象作为参数传入,再在该方法里打印对应的属性与字段。
using System;
using System.Reflection;
using MemberAccessor;

namespace Study
{
    public class Test
    {
        public string stringField;

        public int IntProperty { get; set; }

        public Test(int prop )
        {
            stringField = "stringField value";

            IntProperty = prop;
        }
    } 

    public class Program
    {
        public static void Main(string[] args)
        {
            InstanceDelegateAccessor ida = null;

            int total = 10; //多个相同的类,采用同一个Serializer即可
            for (int i = 0; i < total; ++i)
            {
                //新建的Object
                Test test = new Test(i);
                if (ida == null)
                {
                    //没有Serializer,新建
                    ida = new InstanceDelegateAccessor(test.GetType(), BindingFlags.Public | BindingFlags.Instance,
                                                       BindingFlags.Instance | BindingFlags.Public);
                }
                
                //返回的结果:
                var results = ida.GetValues(test);
                foreach (var result in results)
                {
                    Console.WriteLine(result.Item1 + ": " + result.Item2);
                }
                Console.WriteLine();
            }
        }
    }
}



有一点不足的是,如果一个类里引用了另一个类的对象,这样的话,就只能调用默认的ToString函数了,而不是这个对象的属性了。
比如说:
class A{
    public B  b;
}

class B{
    public int Bid{get;set;}
}

则一旦我去获取A的所有字段,那么对于A里的b对象,我就只能得到b的ToString()方法返回的结果,我无法得到B里的属性Bid。


有知道如何获得的朋友,希望交流。


参考:http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#di


<think>我们被要求回答关于C#中深拷贝的实现方法。根据引用内容,我们可以总结出几种常见的深拷贝方法: 1. 通过序列化(如BinaryFormatter、XmlSerializer等)实现深拷贝:这种方式简单但性能较差。 2. 通过反射实现深拷贝:性能比序列化好,但代码相对复杂。 3. 通过表达式树实现深拷贝:性能最好,但实现起来最复杂。 此外,引用中还提到了其他方法,比如实现ICloneable接口并手动编写深拷贝代码,或者使用第三方库。 我们根据引用整理如下: 引用[1]提到: - 序列化方式:代码量少,性能损耗大。 - 反射方式:性能提升五倍左右(相对于序列化)。 - 表达式树:性能提升二十倍左右(相对于序列化)。 引用[2]说明了深拷贝的概念:复制对象及其引用的所有对象,使得拷贝对象和源对象相互独立。 引用[3]给出了使用深拷贝的准则:当需要避免修改原始对象、原始对象和副本同时使用、原始对象包含引用类型成员时使用深拷贝。 引用[4]展示了一个使用DeepClone方法的例子。 因此,我们可以给出以下实现方法: ### 1. 使用序列化(以BinaryFormatter为例,注意:BinaryFormatter在.NET Core及更高版本中已不被推荐,但在.NET Framework中可用) ```csharp using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; public static T DeepClone<T>(T obj) { using (var ms = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Seek(0, SeekOrigin.Begin); return (T)formatter.Deserialize(ms); } } ``` ### 2. 使用反射(递归复制所有字段和属性) 这种方法需要遍历对象的所有字段(包括私有字段)和属性,并递归复制引用类型对象。以下是一个简化版本: ```csharp public static T DeepCopyByReflection<T>(T obj) { Type type = obj.GetType(); // 如果是值类型或字符串,直接返回 if (type.IsValueType || type == typeof(string)) { return obj; } // 处理数组 if (type.IsArray) { Type elementType = type.GetElementType(); var array = obj as Array; Array copied = Array.CreateInstance(elementType, array.Length); for (int i = 0; i < array.Length; i++) { copied.SetValue(DeepCopyByReflection(array.GetValue(i)), i); } return (T)(object)copied; } // 处理引用类型 object result = Activator.CreateInstance(type); FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { // 跳过只读字段(如initonly字段) if (field.IsInitOnly) continue; object fieldValue = field.GetValue(obj); if (fieldValue != null) { field.SetValue(result, DeepCopyByReflection(fieldValue)); } } return (T)result; } ``` ### 3. 使用表达式树(性能最优) 表达式树实现深拷贝比较复杂,通常需要预先为每个类型编译一个拷贝委托。这里给出一个概念性的示例: ```csharp // 由于表达式树的构建较为复杂,通常我们会缓存每个类型的拷贝委托 public static class DeepCopyExpression { private static readonly Dictionary<Type, Delegate> _cachedCopiers = new Dictionary<Type, Delegate>(); public static T DeepCopy<T>(T original) { if (original == null) return default; Type type = typeof(T); if (!_cachedCopiers.TryGetValue(type, out var copier)) { copier = CreateCopier(type); _cachedCopiers[type] = copier; } return ((Func<T, T>)copier)(original); } private static Delegate CreateCopier(Type type) { // 构建表达式树:创建一个新的对象,然后复制每个字段 // 这里省略具体实现,因为代码较长且复杂 // 可以参考开源项目,比如FastDeepCloner等 throw new NotImplementedException("表达式树的实现较为复杂,建议参考现有库或使用反射版本。"); } } ``` 实际上,表达式树的实现通常会利用Expression类来动态构建拷贝逻辑,并为每个类型生成一个高效的拷贝函数。但这需要大量的代码,因此在实际开发中,我们可能会选择使用现成的库。 ### 4. 实现ICloneable接口(手动实现) 我们可以让需要深拷贝的类实现ICloneable接口,然后在Clone方法中编写深拷贝逻辑。 ```csharp public class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public Address Address { get; set; } // 引用类型 public object Clone() { Person cloned = (Person)this.MemberwiseClone(); // 先浅拷贝 // 对引用类型字段进行深拷贝 cloned.Address = (Address)this.Address.Clone(); // 假设Address也实现了ICloneable return cloned; } } ``` 注意:MemberwiseClone是浅拷贝,所以对于引用类型字段,我们需要手动调用它们的深拷贝方法(例如,如果Address也实现了ICloneable,则调用其Clone方法)。 ### 5. 使用第三方库 一些第三方库提供了深拷贝功能,例如: - AutoMapper:可以通过配置映射来实现深拷贝。 - Newtonsoft.Json:通过序列化和反序列化(JSON)实现深拷贝(类似于序列化方法)。 - FastDeepCloner:一个专门用于深拷贝的高性能库。 例如,使用Newtonsoft.Json: ```csharp using Newtonsoft.Json; public static T DeepCopyJson<T>(T obj) { return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj)); } ``` ### 总结 根据需求选择深拷贝方法: - 如果需要简单实现且不关心性能,可以使用序列化或JSON序列化方法。 - 如果追求性能,可以使用反射或表达式树。 - 对于特定类,实现ICloneable接口是可控的方式。 - 考虑使用第三方库。 注意:序列化方法需要注意类型必须是可序列化的(标记为[Serializable]或使用支持的类型)。 [^1] [^2] [^3] [^4] §§相关问题§§ 1.C#中,深拷贝和浅拷贝的主要区别是什么? 2. 使用序列化实现深拷贝时需要注意哪些问题? 3. 如何为包含循环引用的对象实现深拷贝? 4..NET Core中,为什么BinaryFormatter不被推荐使用?有哪些替代方案? </think>### C#对象深拷贝的实现方法 在C#中实现类对象的深拷贝,主要有以下几种方法: --- #### 1. **序列化方式** 通过对象序列化和反序列化实现深拷贝,代码简洁但性能较低: ```csharp using System.IO; using System.Runtime.Serialization.Formatters.Binary; public static T DeepClone<T>(T obj) { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Position = 0; return (T)formatter.Deserialize(ms); } } ``` **特点**: - 代码量少(约10行) - 需要类型标记`[Serializable]` - 性能较差(引用[1]提到"性能损耗较大") - `.NET Core 3.0+`中`BinaryFormatter`已标记为不安全 --- #### 2. **反射方式** 通过反射递归复制所有字段,性能优于序列化: ```csharp public static T DeepCopyReflection<T>(T original) { if (original == null) return default; Type type = original.GetType(); object copy = Activator.CreateInstance(type); foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { object value = field.GetValue(original); if (value != null) field.SetValue(copy, DeepCopyReflection(value)); } return (T)copy; } ``` **特点**: - 无需`[Serializable]`属性 - 性能提升约5倍(引用[1]) - 无法处理只读字段/循环引用 --- #### 3. **表达式树方式** 高性能方案(比序列化快20倍),需预编译表达式树: ```csharp public static class DeepCopyExpression { private static readonly Dictionary<Type, Delegate> _cachedCopiers = new(); public static T Copy<T>(T source) { if (source == null) return default; Type type = typeof(T); if (!_cachedCopiers.TryGetValue(type, out var copier)) { copier = BuildCopier(type); _cachedCopiers[type] = copier; } return ((Func<T, T>)copier)(source); } private static Delegate BuildCopier(Type type) { // 表达式树构建逻辑(此处简化) // 实际需生成:newObj.Field = original.Field 的赋值树 throw new NotImplementedException("需实现表达式树构造"); } } ``` **特点**: - 性能最优(引用[1]提"性能提升二十倍") - 首次调用需编译表达式树 - 适合高频调用场景 --- #### 4. **手动实现ICloneable** 最可控的方式,需为每个类编写拷贝逻辑: ```csharp public class Person : ICloneable { public string Name; public Address Address; // 引用类型成员 public object Clone() { var copy = new Person { Name = this.Name, Address = (Address)this.Address.Clone() // 递归深拷贝 }; return copy; } } public class Address : ICloneable { ... } // 同样实现Clone ``` **特点**: - 完全控制拷贝过程 - 需为每个引用类型成员实现`ICloneable` - 可处理循环引用等复杂场景 --- #### 5. **JSON序列化替代方案** 使用Newtonsoft.Json实现跨平台深拷贝: ```csharp using Newtonsoft.Json; public static T DeepCopyJson<T>(T obj) => JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj)); ``` **特点**: - 支持`.NET Core/5+` - 无需`[Serializable]` - 性能介于序列化和反射之间 --- ### 方法选择建议 | 方法 | 适用场景 | 性能 | 实现复杂度 | |--------------------|----------------------------------|--------|------------| | **序列化** | 简单对象、低频调用 | ★☆☆☆☆ | ★☆☆☆☆ | | **反射** | 未知类型结构、中等频率 | ★★★☆☆ | ★★★☆☆ | | **表达式树** | 高频调用、性能敏感场景 | ★★★★★ | ★★★★★ | | **手动ICloneable** | 精确控制拷贝过程、复杂对象 | ★★★★☆ | ★★★☆☆ | | **JSON序列化** | 跨平台需求、避免BinaryFormatter | ★★☆☆☆ | ★☆☆☆☆ | > 当对象包含引用类型成员时,必须使用深拷贝保证副本独立性(引用[3])。对于不可变对象或临时副本,浅拷贝可能更合适(引用[3])。 [^1] [^2] [^3] [^4]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值