Como copiar profundamente entre objects de diferentes tipos em C # .NET

Eu tenho um requisito para mapear todos os valores de campo e collections filho entre ObjectV1 e ObjectV2 pelo nome do campo. ObjectV2 está em um namspace diferente para ObjectV1.

A hereditariedade entre o modelo ClassV1 e ClassV2 foi descontada, pois essas duas classs precisam evoluir de forma independente. Considerei usar a reflection (que é lenta) e a serialização binária (que também é lenta) para executar o mapeamento das propriedades comuns.

Existe uma abordagem preferida? Há alguma outra alternativa?

Como uma alternativa ao uso da reflection toda vez, você poderia criar uma class auxiliar que cria dinamicamente methods de cópia usando o Reflection.Emit – isso significa que você só obtém o desempenho atingido na boot. Isso pode fornecer a combinação de flexibilidade e desempenho de que você precisa.

Como o Reflection.Emit é bastante desajeitado, eu sugeriria verificar este suplemento do Reflector, que é shiny para construir esse tipo de código.

Qual versão do .NET é?

Para cópia superficial:

No 3.5, você pode pré-compilar uma Expression para fazer isso. Na versão 2.0, você pode usar o HyperDescriptor muita facilidade para fazer o mesmo. Ambos irão superar a reflection.

Existe uma implementação pré- MiscUtil abordagem Expression em MiscUtilPropertyCopy :

 DestType clone = PropertyCopy.CopyFrom(original); 

(final raso)

BinaryFormatter (na questão) não é uma opção aqui – simplesmente não funcionará, pois os tipos original e de destino são diferentes. Se os dados forem baseados em contrato, XmlSerializer ou DataContractSerializer funcionará se todos os nomes de contrato coincidirem, mas as duas opções (superficiais) acima serão muito mais rápidas se forem possíveis.

Além disso – se seus tipos estiverem marcados com atributos de serialização comuns ( XmlType ou DataContract ), então o protobuf-net pode (em alguns casos) fazer um copy / change type profundo para você:

 DestType clone = Serializer.ChangeType(original); 

Mas isso depende dos tipos que possuem esquemas muito semelhantes (na verdade, ele não usa os nomes, usa o “Order” explícito, etc., nos atributos)

Você pode querer dar uma olhada no AutoMapper , uma biblioteca especializada em copiar valores entre objects. Ele usa convenção sobre configuração, portanto, se as propriedades realmente tiverem os mesmos nomes, ele fará quase todo o trabalho para você.

Aqui está uma solução que eu construí:

  ///  /// Copies the data of one object to another. The target object gets properties of the first. /// Any matching properties (by name) are written to the target. ///  /// The source object to copy from /// The target object to copy to public static void CopyObjectData(object source, object target) { CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.Instance); } ///  /// Copies the data of one object to another. The target object gets properties of the first. /// Any matching properties (by name) are written to the target. ///  /// The source object to copy from /// The target object to copy to /// A comma delimited list of properties that should not be copied /// Reflection binding access public static void CopyObjectData(object source, object target, string excludedProperties, BindingFlags memberAccess) { string[] excluded = null; if (!string.IsNullOrEmpty(excludedProperties)) { excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); } MemberInfo[] miT = target.GetType().GetMembers(memberAccess); foreach (MemberInfo Field in miT) { string name = Field.Name; // Skip over excluded properties if (string.IsNullOrEmpty(excludedProperties) == false && excluded.Contains(name)) { continue; } if (Field.MemberType == MemberTypes.Field) { FieldInfo sourcefield = source.GetType().GetField(name); if (sourcefield == null) { continue; } object SourceValue = sourcefield.GetValue(source); ((FieldInfo)Field).SetValue(target, SourceValue); } else if (Field.MemberType == MemberTypes.Property) { PropertyInfo piTarget = Field as PropertyInfo; PropertyInfo sourceField = source.GetType().GetProperty(name, memberAccess); if (sourceField == null) { continue; } if (piTarget.CanWrite && sourceField.CanRead) { object targetValue = piTarget.GetValue(target, null); object sourceValue = sourceField.GetValue(source, null); if (sourceValue == null) { continue; } if (sourceField.PropertyType.IsArray && piTarget.PropertyType.IsArray && sourceValue != null ) { CopyArray(source, target, memberAccess, piTarget, sourceField, sourceValue); } else { CopySingleData(source, target, memberAccess, piTarget, sourceField, targetValue, sourceValue); } } } } } private static void CopySingleData(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object targetValue, object sourceValue) { //instantiate target if needed if (targetValue == null && piTarget.PropertyType.IsValueType == false && piTarget.PropertyType != typeof(string)) { if (piTarget.PropertyType.IsArray) { targetValue = Activator.CreateInstance(piTarget.PropertyType.GetElementType()); } else { targetValue = Activator.CreateInstance(piTarget.PropertyType); } } if (piTarget.PropertyType.IsValueType == false && piTarget.PropertyType != typeof(string)) { CopyObjectData(sourceValue, targetValue, "", memberAccess); piTarget.SetValue(target, targetValue, null); } else { if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName) { object tempSourceValue = sourceField.GetValue(source, null); piTarget.SetValue(target, tempSourceValue, null); } else { CopyObjectData(piTarget, target, "", memberAccess); } } } private static void CopyArray(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object sourceValue) { int sourceLength = (int)sourceValue.GetType().InvokeMember("Length", BindingFlags.GetProperty, null, sourceValue, null); Array targetArray = Array.CreateInstance(piTarget.PropertyType.GetElementType(), sourceLength); Array array = (Array)sourceField.GetValue(source, null); for (int i = 0; i < array.Length; i++) { object o = array.GetValue(i); object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetElementType()); CopyObjectData(o, tempTarget, "", memberAccess); targetArray.SetValue(tempTarget, i); } piTarget.SetValue(target, targetArray, null); } 

Se a velocidade é um problema, você poderia colocar o processo de reflection offline e gerar código para o mapeamento das propriedades comuns. Você poderia fazer isso em tempo de execução usando Lightweight Code Generation ou completamente offline, construindo o código C # para compilar.

Se você controlar a instanciação do object de destino, tente usar o JavaScriptSerializer . Não cita qualquer tipo de informação.

 new JavaScriptSerializer().Serialize(new NamespaceA.Person{Id = 1, Name = "A"}) 

retorna

 {Id: 1, Name: "A"} 

A partir disso, deve ser possível desserializar qualquer class com os mesmos nomes de propriedade.

Se a velocidade é um problema, você deve implementar methods clone nos próprios methods.