Bei 2 Objekten A und B vom Typ T möchte ich die Werte der Eigenschaften in A denselben Eigenschaften in B zuweisen, ohne für jede Eigenschaft eine explizite Zuweisung vorzunehmen.
Ich möchte Code wie folgt speichern:
b.Nombre = a.Nombre;
b.Descripcion = a.Descripcion;
b.Imagen = a.Imagen;
b.Activo = a.Activo;
so etwas tun
a.ApplyProperties(b);
Ist es möglich?
c#
linq-to-sql
eKek0
quelle
quelle
Antworten:
Ich habe einen Typ in
MiscUtil
aufgerufen,PropertyCopy
der etwas Ähnliches tut - obwohl er eine neue Instanz des Zieltyps erstellt und die Eigenschaften in diesen kopiert.Die Typen müssen nicht identisch sein - es werden lediglich alle lesbaren Eigenschaften vom Typ "Quelle" in den Typ "Ziel" kopiert. Wenn die Typen gleich sind, funktioniert das natürlich eher :) Es ist übrigens eine flache Kopie.
Im Codeblock am Ende dieser Antwort habe ich die Funktionen der Klasse erweitert. Zum Kopieren von einer Instanz in eine andere
PropertyInfo
werden zur Ausführungszeit einfache Werte verwendet. Dies ist langsamer als die Verwendung eines Ausdrucksbaums. Die Alternative wäre jedoch, eine dynamische Methode zu schreiben, auf die ich nicht allzu scharf bin. Wenn die Leistung für Sie absolut kritisch ist, lassen Sie es mich wissen und ich werde sehen, was ich tun kann. Um die Methode zu verwenden, schreiben Sie etwas wie:MyType instance1 = new MyType(); // Do stuff MyType instance2 = new MyType(); // Do stuff PropertyCopy.Copy(instance1, instance2);
(Wo
Copy
ist eine generische Methode, die mit Typinferenz aufgerufen wird).Ich bin nicht wirklich bereit, eine vollständige MiscUtil-Version zu erstellen, aber hier ist der aktualisierte Code, einschließlich der Kommentare. Ich werde sie nicht für den SO-Editor neu verpacken - kopiere einfach den ganzen Teil.
(Ich würde wahrscheinlich auch die API in Bezug auf die Benennung ein wenig neu gestalten, wenn ich von vorne anfangen würde, aber ich möchte bestehende Benutzer nicht brechen ...)
#if DOTNET35 using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace MiscUtil.Reflection { /// <summary> /// Non-generic class allowing properties to be copied from one instance /// to another existing instance of a potentially different type. /// </summary> public static class PropertyCopy { /// <summary> /// Copies all public, readable properties from the source object to the /// target. The target type does not have to have a parameterless constructor, /// as no new instance needs to be created. /// </summary> /// <remarks>Only the properties of the source and target types themselves /// are taken into account, regardless of the actual types of the arguments.</remarks> /// <typeparam name="TSource">Type of the source</typeparam> /// <typeparam name="TTarget">Type of the target</typeparam> /// <param name="source">Source to copy properties from</param> /// <param name="target">Target to copy properties to</param> public static void Copy<TSource, TTarget>(TSource source, TTarget target) where TSource : class where TTarget : class { PropertyCopier<TSource, TTarget>.Copy(source, target); } } /// <summary> /// Generic class which copies to its target type from a source /// type specified in the Copy method. The types are specified /// separately to take advantage of type inference on generic /// method arguments. /// </summary> public static class PropertyCopy<TTarget> where TTarget : class, new() { /// <summary> /// Copies all readable properties from the source to a new instance /// of TTarget. /// </summary> public static TTarget CopyFrom<TSource>(TSource source) where TSource : class { return PropertyCopier<TSource, TTarget>.Copy(source); } } /// <summary> /// Static class to efficiently store the compiled delegate which can /// do the copying. We need a bit of work to ensure that exceptions are /// appropriately propagated, as the exception is generated at type initialization /// time, but we wish it to be thrown as an ArgumentException. /// Note that this type we do not have a constructor constraint on TTarget, because /// we only use the constructor when we use the form which creates a new instance. /// </summary> internal static class PropertyCopier<TSource, TTarget> { /// <summary> /// Delegate to create a new instance of the target type given an instance of the /// source type. This is a single delegate from an expression tree. /// </summary> private static readonly Func<TSource, TTarget> creator; /// <summary> /// List of properties to grab values from. The corresponding targetProperties /// list contains the same properties in the target type. Unfortunately we can't /// use expression trees to do this, because we basically need a sequence of statements. /// We could build a DynamicMethod, but that's significantly more work :) Please mail /// me if you really need this... /// </summary> private static readonly List<PropertyInfo> sourceProperties = new List<PropertyInfo>(); private static readonly List<PropertyInfo> targetProperties = new List<PropertyInfo>(); private static readonly Exception initializationException; internal static TTarget Copy(TSource source) { if (initializationException != null) { throw initializationException; } if (source == null) { throw new ArgumentNullException("source"); } return creator(source); } internal static void Copy(TSource source, TTarget target) { if (initializationException != null) { throw initializationException; } if (source == null) { throw new ArgumentNullException("source"); } for (int i = 0; i < sourceProperties.Count; i++) { targetProperties[i].SetValue(target, sourceProperties[i].GetValue(source, null), null); } } static PropertyCopier() { try { creator = BuildCreator(); initializationException = null; } catch (Exception e) { creator = null; initializationException = e; } } private static Func<TSource, TTarget> BuildCreator() { ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source"); var bindings = new List<MemberBinding>(); foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!sourceProperty.CanRead) { continue; } PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name); if (targetProperty == null) { throw new ArgumentException("Property " + sourceProperty.Name + " is not present and accessible in " + typeof(TTarget).FullName); } if (!targetProperty.CanWrite) { throw new ArgumentException("Property " + sourceProperty.Name + " is not writable in " + typeof(TTarget).FullName); } if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0) { throw new ArgumentException("Property " + sourceProperty.Name + " is static in " + typeof(TTarget).FullName); } if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType)) { throw new ArgumentException("Property " + sourceProperty.Name + " has an incompatible type in " + typeof(TTarget).FullName); } bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty))); sourceProperties.Add(sourceProperty); targetProperties.Add(targetProperty); } Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings); return Expression.Lambda<Func<TSource, TTarget>>(initializer, sourceParameter).Compile(); } } } #endif
quelle
PropertyCopier
. Um ehrlich zu sein, müsste ich ein bisschen mehr darüber nachdenken und einige Anwendungsfälle sehen, um es richtig zu gestalten.Weil ich glaube, dass Jons Version ein bisschen zu kompliziert ist und Steves Version zu einfach ist und ich Daniels Idee einer Erweiterungsklasse mag.
Außerdem ist eine generische Version hübsch, aber unnötig, da alle Elemente Objekte sind.
Ich möchte meine schlanke und gemeine Version freiwillig zur Verfügung stellen. Dank an alle oben genannten. : D.
Code:
using System; using System.Reflection; /// <summary> /// A static class for reflection type functions /// </summary> public static class Reflection { /// <summary> /// Extension for 'Object' that copies the properties to a destination object. /// </summary> /// <param name="source">The source.</param> /// <param name="destination">The destination.</param> public static void CopyProperties(this object source, object destination) { // If any this null throw an exception if (source == null || destination == null) throw new Exception("Source or/and Destination Objects are null"); // Getting the Types of the objects Type typeDest = destination.GetType(); Type typeSrc = source.GetType(); // Iterate the Properties of the source instance and // populate them from their desination counterparts PropertyInfo[] srcProps = typeSrc.GetProperties(); foreach (PropertyInfo srcProp in srcProps) { if (!srcProp.CanRead) { continue; } PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name); if (targetProperty == null) { continue; } if (!targetProperty.CanWrite) { continue; } if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate) { continue; } if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0) { continue; } if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)) { continue; } // Passed all tests, lets set the value targetProperty.SetValue(destination, srcProp.GetValue(source, null), null); } } }
Verwendung:
/// <summary> /// ExampleCopyObject /// </summary> /// <returns></returns> public object ExampleCopyObject() { object destObject = new object(); this.CopyProperties(destObject); // inside a class you want to copy from Reflection.CopyProperties(this, destObject); // Same as above but directly calling the function TestClass srcClass = new TestClass(); TestStruct destStruct = new TestStruct(); srcClass.CopyProperties(destStruct); // using the extension directly on a object Reflection.CopyProperties(srcClass, destObject); // Same as above but directly calling the function //so on and so forth.... your imagination is the limits :D return srcClass; } public class TestClass { public string Blah { get; set; } } public struct TestStruct { public string Blah { get; set; } }
Da ich gelangweilt war und eine Linq-Version durch einen Kommentar vorgeschlagen wurde
using System; using System.Linq; using System.Reflection; /// <summary> /// A static class for reflection type functions /// </summary> public static class Reflection { /// <summary> /// Extension for 'Object' that copies the properties to a destination object. /// </summary> /// <param name="source">The source.</param> /// <param name="destination">The destination.</param> public static void CopyProperties(this object source, object destination) { // If any this null throw an exception if (source == null || destination == null) throw new Exception("Source or/and Destination Objects are null"); // Getting the Types of the objects Type typeDest = destination.GetType(); Type typeSrc = source.GetType(); // Collect all the valid properties to map var results = from srcProp in typeSrc.GetProperties() let targetProperty = typeDest.GetProperty(srcProp.Name) where srcProp.CanRead && targetProperty != null && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate) && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0 && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType) select new { sourceProperty = srcProp, targetProperty = targetProperty }; //map the properties foreach (var props in results) { props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null); } } }
quelle
Hier ist eine kurze und süße Version, da Sie sagten, dass beide Objekte vom gleichen Typ sind:
foreach (PropertyInfo property in typeof(YourType).GetProperties().Where(p => p.CanWrite)) { property.SetValue(targetObject, property.GetValue(sourceObject, null), null); }
quelle
Aufbauend auf Steves Methode habe ich mich für den Ansatz der Erweiterungsmethode entschieden. Dies verwendet meine Basisklasse als Typ, sollte aber auch unter Verwendung von Objekt als Parametertyp verwendbar sein. Funktioniert hervorragend für meine Zwecke.
using System.Reflection; //*Namespace Here* public static class Ext { public static void CopyProperties(this EntityBase source, EntityBase destination) { // Iterate the Properties of the destination instance and // populate them from their source counterparts PropertyInfo[] destinationProperties = destination.GetType().GetProperties(); foreach (PropertyInfo destinationPi in destinationProperties) { PropertyInfo sourcePi = source.GetType().GetProperty(destinationPi.Name); destinationPi.SetValue(destination, sourcePi.GetValue(source, null), null); } } }
Die Verwendung sieht folgendermaßen aus:
Jetzt hat Item2 die gleichen Eigenschaftsdaten wie Item1.
quelle
Änderung der Daniel-Version, um Ausnahmen zu vermeiden.
foreach (PropertyInfo property in typeof(YourType).GetProperties()) { if (property.CanWrite) { property.SetValue(marketData, property.GetValue(market, null), null); } }
quelle
Diese kurze und einfache Erweiterungsmethode ermöglicht es Ihnen, übereinstimmende Eigenschaften mit der Prüfung des Nullwerts von einem Objekt in ein anderes zu kopieren und ist beschreibbar.
public static void CopyPropertiesTo(this object fromObject, object toObject) { PropertyInfo[] toObjectProperties = toObject.GetType().GetProperties(); foreach (PropertyInfo propTo in toObjectProperties) { PropertyInfo propFrom = fromObject.GetType().GetProperty(propTo.Name); if (propFrom!=null && propFrom.CanWrite) propTo.SetValue(toObject, propFrom.GetValue(fromObject, null), null); } }
quelle
Grundsätzlich sollten wir 2019 wahrscheinlich aktuellere Sprachfunktionen wie Ausdrucksbäume und kompilierte Lambda-Ausdrücke anstelle von Reflection verwenden
Da ich keinen "flachen Kloner" finden konnte, der meinen Anforderungen entspricht (vor allem Geschwindigkeit), habe ich beschlossen, selbst einen zu erstellen. Es listet alle gettable / Settable-Eigenschaften auf und erstellt dann einen
Block
Ausdruck, der dann kompiliert und zwischengespeichert wird. Damit ist es fast 13-mal schneller als der beliebte AutoMapper. Die Verwendung ist sehr einfach:Die vollständige Quelle finden Sie hier: https://github.com/jitbit/PropMapper
quelle
Sie können die Serialisierung verwenden, um das Objekt tief zu klonen:
public static T DeepClone<T>(this T objectToClone) where T: BaseClass { BinaryFormatter bFormatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); bFormatter.Serialize(stream, objectToClone); stream.Seek(0, SeekOrigin.Begin); T clonedObject = (T)bFormatter.Deserialize(stream); return clonedObject; }
Klassen müssten natürlich nur als serialisierbar markiert werden.
quelle
Serializable
, können Sie JSONEs gibt ICloneable und object.MemberwiseClone (flache Kopie) (diese erstellen ein ganz neues Objekt und erfüllen möglicherweise nicht Ihre Anforderungen).
Sie können Reflection verwenden, um es selbst zu tun (von einer Basisklasse erben, damit Sie es nicht erneut implementieren müssen).
Oder Sie könnten Code generieren.
quelle
Seit einigen Jahren verwende ich eine beliebte Bibliothek mit dem Namen ValueInjecter
Nuget: https://www.nuget.org/packages/ValueInjecter/
github: https://github.com/omuleanu/ValueInjecter
target.InjectFrom(source); target.InjectFrom<Injection>(source); target.InjectFrom(new Injection(parameters), source); target.InjectFrom<Injection>(); // without source
Beachten Sie, dass selbst wenn die grundlegenden Lösungen ziemlich einfach sind (siehe andere Antworten), es viele Randfälle (z. B. Deep Copy, Generics, Nullwerte) und Optimierungen (z. B. Cache der reflektierten Eigenschaften) gibt, sodass Sie die gepflegte Bibliothek besser verwenden können.
quelle
Sie könnten so etwas versuchen ...
MyType destination = new MyType(); MyType source = new MyType(); // Iterate the Properties of the destination instance and // populate them from their source counterparts PropertyInfo[] destinationProperties = destination.GetType().GetProperties(); foreach (PropertyInfo destinationPI in destinationProperties) { PropertyInfo sourcePI = source.GetType().GetProperty(destinationPI.Name); destinationPI.SetValue(destination, sourcePI.GetValue(source, null), null); }
quelle
Vielleicht ist dieser Beitrag etwas alt, die Antworten sind gut, aber wenn Sie viele Objekte einem Zieltyp zuordnen müssen, kann das Durchlaufen von Eigenschaften kostspielig sein (Imaging 100 Requisiten und mehr).
Ich verwende diese AutoMapFactory-Methode (nur LinQ erforderlich), sie iteriert nur einmal in den Eigenschaften und die Zuordnung jedes Objekts ist schnell (100000 Requisiten / Sekunden).
private Func<S,T> AutoMapFactory<S,T>() where T: class, new() where S : class { List<Action<T, S>> mapActions = typeof(T).GetProperties().Where(tp => tp.CanWrite) .SelectMany(tp => typeof(S).GetProperties().Where(sp => sp.CanRead) .Where(sp => sp.Name == tp.Name && tp.PropertyType.IsAssignableFrom(sp.PropertyType)) .Select(sp => (Action<T,S>)((targetObj, sourceObj) => tp.SetValue(targetObj, sp.GetValue(sourceObj))))) .ToList(); return sourceObj => { if (sourceObj == null) return null; T targetObj = new T(); mapActions.ForEach(action => action(targetObj, sourceObj)); return targetObj; }; }
Wie man das benutzt:
... var autoMapper = AutoMapFactory<SourceType, TargetType>(); //Get Only 1 instance of the mapping function ... someCollection.Select(item => autoMapper(item)); //Almost instantaneous ...
quelle
Wenn Sie so etwas wie ApplyProperties möchten, können Sie eine Erweiterungsmethode für Object schreiben, die genau das tut, was Sie benötigen. Stellen Sie nur fest, dass eine solche Erweiterungsmethode nicht "rein" oder nebenwirkungsfrei wäre. Aber wenn Sie die Fähigkeit benötigen, ist es eine Möglichkeit, dies zu erreichen.
quelle
Als ich die Antwort von @Azerothians erweiterte, brauchte ich einige zusätzliche Anforderungen:
wollte wissen, ob Eigenschaften kopiert wurden oder nicht (um festzustellen, ob ein Datenbank-Update erforderlich wäre)
/// <summary> /// Extension for 'Object' that copies the properties to a destination object. /// </summary> /// <param name="source">The source.</param> /// <param name="destination">The destination.</param> /// <param name="PropertiesToIgnore">an optional list of property names which will NOT be copied</param> /// <param name="IgnoreNullProperties">when true will not update properties where the source is null</param> /// <param name="CoerceDataType">when true, will attempt to coerce the source property to the destination property (e.g. int to decimal) </param> /// <param name="ThrowOnTypeMismatch">when true, will throw a InvalidCastException if the data cannot be coerced</param> /// <exception cref="InvalidCastException">if there is a data type mismatch between source/destination and ThrowOnTypeMismatch is enabled and unable to coerce the data type.</exception> /// <returns>true if any properties were changed</returns> public static bool CopyProperties(this object source, object destination, IEnumerable<string> PropertiesToIgnore = null, bool IgnoreNullProperties = false, bool ThrowOnTypeMismatch = true, bool CoerceDataType = true) { if (source is null) throw new ArgumentNullException(nameof(source)); if (destination is null) throw new ArgumentNullException(nameof(destination)); // Getting the Types of the objects Type typeDest = destination.GetType(); Type typeSrc = source.GetType(); // Collect all the valid properties to map var results = (from srcProp in typeSrc.GetProperties() let targetProperty = typeDest.GetProperty(srcProp.Name) where srcProp.CanRead && targetProperty != null && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate) && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0 && !( from i in PropertiesToIgnore ?? Enumerable.Empty<string>() select i ).Contains(srcProp.Name) && (!IgnoreNullProperties || srcProp.GetValue(source, null) != null) select new { sourceProperty = srcProp, targetProperty = targetProperty }).ToList(); bool PropertyChanged = false; //map the properties foreach (var props in results) { var srcValue = props.sourceProperty.GetValue(source, null); var dstValue = props.targetProperty.GetValue(destination, null); if (props.targetProperty.PropertyType.IsAssignableFrom(props.sourceProperty.PropertyType)) props.targetProperty.SetValue(destination, srcValue, null); else { try { if (!CoerceDataType) throw new InvalidCastException($"Types do not match, source: {props.sourceProperty.PropertyType.FullName}, target: {props.targetProperty.PropertyType.FullName}."); if (srcValue != null) { // determine if nullable type Type tgtType = Nullable.GetUnderlyingType(props.targetProperty.PropertyType); // if it is, use the underlying type // without this we cannot convert int? -> decimal? when value is not null if (tgtType != null) props.targetProperty.SetValue(destination, Convert.ChangeType(srcValue, tgtType, CultureInfo.InvariantCulture), null); else // otherwise use the original type props.targetProperty.SetValue(destination, Convert.ChangeType(srcValue, props.targetProperty.PropertyType, CultureInfo.InvariantCulture), null); } else // if null we can just set it as null props.targetProperty.SetValue(destination, null, null); } catch (Exception ex) { if (ThrowOnTypeMismatch) throw new InvalidCastException($"Unable to copy property {props.sourceProperty.Name} with value {srcValue} from object of type ({typeSrc.FullName}) to type ({typeDest.FullName}), Error: {ex.Message}"); // else ignore update } var newdstValue = props.targetProperty.GetValue(destination, null); if (newdstValue == null && dstValue != null || !newdstValue.Equals(dstValue)) PropertyChanged = true; } } return PropertyChanged; }
quelle
public TestClass { public TestName {get;set;} } public void submain() { var originalTestClass = new TestClass() { TestName ="Test Name"; }; var newTestClass = new TestClass(); newTestClass.CopyPropertiesFrom(originalTestClass); }
quelle