net - what can be done with reflection in c#
Cómo copiar profundamente entre objetos de diferentes tipos en C#.NET (7)
Como alternativa al uso de la reflexión cada vez, puede crear una clase de ayuda que crea dinámicamente métodos de copia mediante Reflection.Emit: esto significa que solo obtendrá el rendimiento al iniciarse. Esto puede darle la combinación de flexibilidad y rendimiento que necesita.
Como Reflection.Emit es bastante torpe, sugeriría que revises este complemento Reflector, que es genial para construir este tipo de código.
Tengo un requisito para asignar todos los valores de campo y las colecciones secundarias entre ObjectV1 y ObjectV2 por nombre de campo. ObjectV2 está en un namspace diferente a ObjectV1.
La herencia entre la plantilla ClassV1 y ClassV2 se ha descartado ya que estas 2 clases necesitan evolucionar de forma independiente. He considerado utilizar la reflexión (que es lenta) y la serialización binaria (que también es lenta) para realizar el mapeo de las propiedades comunes.
¿Hay un enfoque preferido? ¿Hay otras alternativas?
Si la velocidad es un problema, debe implementar métodos de clonación en los métodos mismos.
Si la velocidad es un problema, puede desconectar el proceso de reflexión y generar código para la asignación de las propiedades comunes. Puede hacer esto en tiempo de ejecución usando Lightweight Code Generation o completamente fuera de línea al compilar código C # para compilar.
Aquí hay una solución que construí:
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source object to copy from</param>
/// <param name="target">The target object to copy to</param>
public static void CopyObjectData(object source, object target)
{
CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.Instance);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source object to copy from</param>
/// <param name="target">The target object to copy to</param>
/// <param name="excludedProperties">A comma delimited list of properties that should not be copied</param>
/// <param name="memberAccess">Reflection binding access</param>
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);
}
Es posible que desee echarle un vistazo a AutoMapper , una biblioteca que se especializa en copiar valores entre objetos. Utiliza la convención sobre la configuración, por lo que si las propiedades realmente tienen exactamente los mismos nombres, hará casi todo el trabajo por usted.
Si usted controla la creación de instancias del objeto de destino, intente con JavaScriptSerializer . No escupe ningún tipo de información.
new JavaScriptSerializer().Serialize(new NamespaceA.Person{Id = 1, Name = "A"})
devoluciones
{Id: 1, Name: "A"}
A partir de esto, es posible deserializar cualquier clase con los mismos nombres de propiedad.
¿Qué versión de .NET es?
Para una copia superficial:
En 3.5, puede precompilar una Expression
para hacer esto. En 2.0, puede usar HyperDescriptor
muy fácilmente para hacer lo mismo. Ambos superarán enormemente la reflexión.
Hay una implementación pre-enlatada del enfoque Expression
en MiscUtil
- PropertyCopy
:
DestType clone = PropertyCopy<DestType>.CopyFrom(original);
(final superficial)
BinaryFormatter (en la pregunta) no es una opción aquí, simplemente no funcionará ya que los tipos originales y de destino son diferentes. Si los datos están basados en contratos, XmlSerializer o DataContractSerializer funcionarían si todos los nombres de los contratos coinciden, pero las dos opciones (superficiales) anteriores serían mucho más rápidas si son posibles.
Además, si sus tipos están marcados con atributos de serialización comunes ( XmlType
o DataContract
), entonces protobuf-net puede (en algunos casos) hacer una copia profunda / cambiar de tipo para usted:
DestType clone = Serializer.ChangeType<OriginalType, DestType>(original);
Pero esto depende de que los tipos tengan esquemas muy similares (de hecho, no usa los nombres, usa el "Orden" explícito, etc. en los atributos)