tutorial expresiones c# .net lambda

expresiones - linq c#



¿Cómo simplificar la repetición de la construcción if-then-assign? (15)

Use T4 para la metaprogramación

Otro enfoque: muy a menudo cuando tenemos un código duplicado que es realmente simple y probablemente muy rápido . En este caso, cada bloque duplicado if no es el mismo, tiene un poco de conocimiento, el mapeo de una propiedad a otra.

Es molesto escribir y mantener los bloques duplicados.
Una forma de evitar escribir código repetitivo útil es generarlo automáticamente.

Con mi solución, el mapeo es sencillo:

var mappings = new []{ new Mapper("ProductModel", "Product") { "Title", // ProductModel.Title goes to Product.Title {"Id", "ServiceId"}, // ProductModel.Id goes to Product.ServiceId }, };

Aquí hay una plantilla de texto t4 (función incorporada a Visual Studio):

<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <# // Consider including the namespace in the class names. // You only need to change the mappings. var product = new Mapper("Product", "ProductEntity") { "Name", {"Id", "ServiceId"} }; var person = new Mapper("Person", "DbPerson") { "Employee", {"Name", "FullName"}, {"Addredd", "HomeAddress"} }; var mappings = new [] {product, person}; #> // !!! // !!! Do not modify this file, it is automatically generated. Change the .tt file instead. !!! // !!! namespace Your.Mapper { partial class Mapper { <# foreach(var mapping in mappings) { #>/// <summary> /// Set <paramref name="target"/> properties by copying them from <paramref name="source"/>. /// </summary> /// <remarks>Mapping:<br/> <#foreach(var property in mapping){ #>/// <see cref="<#=mapping.SourceType#>.<#=property.SourceProperty#>"/> → <see cref="<#=mapping.TargetType#>.<#=property.TargetProperty#>"/> <br/> <#} #>/// </remarks> /// <returns><c>true</c> if any property was changed, <c>false</c> if all properties were the same.</returns> public bool ModifyExistingEntity(<#=mapping.SourceType#> source, <#=mapping.TargetType#> target) { bool dirty = false; <# foreach(var property in mapping) { #>if (target.<#=property.TargetProperty#> != source.<#=property.SourceProperty#>) { dirty = true; target.<#=property.TargetProperty#> = source.<#=property.SourceProperty#>; } <#} #>return dirty; } <# } #> } } <#+ class Mapper : IEnumerable<PropertyMapper> { private readonly List<PropertyMapper> _properties; public Mapper(string sourceType, string targetType) { SourceType = sourceType; TargetType = targetType; _properties = new List<PropertyMapper>(); } public string SourceType { get; set; } public string TargetType { get; set; } public void Add(string fieldName) { _properties.Add(new PropertyMapper {SourceProperty = fieldName, TargetProperty = fieldName}); } public void Add(string sourceProperty, string targetProperty) { _properties.Add(new PropertyMapper { SourceProperty = sourceProperty, TargetProperty = targetProperty }); } IEnumerator<PropertyMapper> IEnumerable<PropertyMapper>.GetEnumerator() { return _properties.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _properties.GetEnumerator(); } } class PropertyMapper { public string SourceProperty { get; set; } public string TargetProperty { get; set; } } #>

Esta plantilla genera el siguiente código: https://gist.github.com/kobi/d52dd1ff27541acaae10

Ventajas:

  • El levantamiento pesado se realiza en tiempo de compilación (en realidad, una vez antes del tiempo de compilación): el código generado es rápido.
  • El código generado está documentado.
  • Fácil de mantener: puede cambiar todos los mapeadores en un solo punto.
  • Los métodos generados están documentados.
  • No hay errores de copiar y pegar .
  • Esto es muy divertido

Desventajas:

  • Uso de cadenas para obtener nombres de propiedad. Tenga en cuenta que esto no es código de producción , solo se usa para generar código. Es posible utilizar los tipos reales y los árboles de Expresión ( un ejemplo a continuación ).
  • El análisis estático probablemente omita el uso en la plantilla (incluso si usamos Expresiones, no todas las herramientas miran en los archivos tt).
  • Mucha gente no sabe lo que está pasando.
  • Si está utilizando expresiones, es complicado hacer referencia a sus tipos.

Notas:

  • He nombrado los argumentos source y target , y cambié su orden para que el source siempre sea primero.

Ha habido cierta preocupación de que estoy usando cadenas en lugar de las propiedades reales. Aunque este es un problema menor en este caso (el resultado se compila), aquí hay una adición que funciona con sus objetos reales.

En la parte superior, agrega esto (el tercero debe ser tu espacio de nombre):

<#@ assembly name="$(TargetPath)" #> <#@ import namespace="System.Linq.Expressions" #> <#@ import namespace="ConsoleApplicationT4So29913514" #>

En la parte inferior, agrega:

class Mapper<TSource, TTarget> : Mapper { public Mapper() : base(typeof(TSource).FullName, typeof(TTarget).FullName) { } private static string GetExpressionMemberAccess(LambdaExpression getProperty) { var member = (MemberExpression)getProperty.Body; //var lambdaParameterName = (ParameterExpression)member.Expression; var lambdaParameterName = getProperty.Parameters[0]; // `x` in `x => x.PropertyName` var labmdaBody = member.ToString(); //will not work with indexer. return labmdaBody.Substring(lambdaParameterName.Name.Length + 1); //+1 to remove the `.`, get "PropertyName" } public void Add<TProperty>(Expression<Func<TSource, TProperty>> getSourceProperty, Expression<Func<TTarget, TProperty>> getTargetProperty) { Add(GetExpressionMemberAccess(getSourceProperty), GetExpressionMemberAccess(getTargetProperty)); } /// <summary> /// The doesn''t really make sense, but we assume we have <c>source=>source.Property</c>, <c>target=>target.Property</c> /// </summary> public void Add<TProperty>(Expression<Func<TSource, TProperty>> getProperty) { Add(GetExpressionMemberAccess(getProperty)); } }

Uso:

var mappings = new Mapper[] { new Mapper<Student,StudentRecord> { {s=>s.Title, t=>t.EntityTitle}, {s=>s.StudentId, t=>t.Id}, s=>s.Name, s=>s.LuckyNumber, }, new Mapper<Car,RaceCar> { c=>c.Color, c=>c.Driver, {c=>c.Driver.Length, r=>r.DriverNameDisplayWidth}, }, };

El archivo completo debería verse así: https://gist.github.com/kobi/6423eaa13cca238447a8
La salida sigue siendo la misma: https://gist.github.com/kobi/3508e9f5522a13e1b66b

Notas:

  • Las expresiones solo se usan para obtener el nombre de la propiedad como una cadena, no las estamos compilando o ejecutándolas.
  • En C # 6 tendremos el operador nameof() , que es un buen compromiso entre Expressions y no-magic-strings.

Tengo el siguiente método:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = false; if (entity.Title != item.Title) { isModified = true; entity.Title = item.Title; } if (entity.ServerId != item.Id) { isModified = true; entity.ServerId = item.Id; } return isModified; }

Me pregunto si podría sugerir una mejor manera de implementar el método.

El problema es obvio: 5 líneas de código casi pegado por propiedad es demasiado. Puede haber una solución que use Func -s / Expression -s fuera de mi visión.


"Better" es subjetivo en este contexto. Como se quejaba del recuento de líneas, tengo una forma más concisa:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = false; isModified |= (entity.Title!= item.Title) ? (entity.Title = item.Title) == item.Title : false; isModified |= (entity.ServerId != item.Id) ? (entity.ServerId = item.Id) == item.Id : false; return isModified; }


Algo como esto debería funcionar

protected bool ModifyExistingEntity(Person entity, ProductModel item) { bool isModified = CompareAndModify(() => entity.Title = item.Title, () => entity.Title != item.Title); isModified |= CompareAndModify(() => entity.ServerId = item.Id, () => entity.ServerId != item.Id); return isModified; } private bool CompareAndModify(Action setter, Func<bool> comparator) { if (comparator()) { setter(); return true; } return false; }

No estoy seguro si esto es legible. Es subjetivo


Continuando con la respuesta de @bstenzel, ¿no debería hacer esto el truco?

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isEntityModified = entity.Title != item.Title || entity.ServerId != item.ServerId; entity.Title = item.Title; entity.ServerId = item.Id; return isEntityModified; }

Limpio y simple.


Creo que estás buscando una forma de comparar el contenido de las propiedades de dos objetos. Su muestra contiene dos propiedades, pero espero que su código real contenga mucho más (ya que una entidad de Product probablemente tenga muchas propiedades).

Debería comenzar escribiendo un método que compare sus dos objetos. Para sus referencias aquí hay algunas preguntas sobre el tema:

  1. Implementar correctamente la comparación de dos objetos con tipos diferentes pero semánticamente equivalentes
  2. Comparando las propiedades del objeto en c #

Tu método se vería así:

public static bool IsEqualTo<TSource>(this TSource sourceObj, TDestination destinationObj) where TSource : class where TDestination : class { // Your comparison code goes here }

Luego, tendrá que escribir un segundo método para copiar datos entre sus objetos. Estas preguntas pueden guiarlo (marque la respuesta de Marc):

  1. Cómo copiar profundamente entre objetos de diferentes tipos en C # .NET

Tu método se vería así:

public static bool CopyDataTo<TSource>(this TSource sourceObj, TDestination destinationObj) where TSource : class where TDestination : class { // Your data copy code goes here }

Tu código final se verá tan simple como

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { if (!entity.IsEqualTo(item)) { item.CopyDataTo(entity); return true; } return false; }


Creo que una extensión de esta respuesta podría funcionar para usted:

public static bool SetIfModified<CLeft, T>(Expression<Func<CLeft, T>> exprLeft, CLeft leftType, T rightValue) { var getterLeft = exprLeft.Compile(); if (EqualityComparer<T>.Default.Equals(getterLeft(leftType), rightValue)) { var newValueLeft = Expression.Parameter(exprLeft.Body.Type); var assignLeft = Expression.Lambda<Action<CLeft, T>>(Expression.Assign(exprLeft.Body, newValueLeft), exprLeft.Parameters[0], newValueLeft); var setterLeft = assignLeft.Compile(); setterLeft(leftType, rightValue); return true; } else { return false; } }

Se necesita una expresión para verificar el valor. Compila y ejecuta dinámicamente.

Úselo así:

public class Product { public string Title { get; set; } } public class ProductModel { public string Title { get; set; } } static void Main(string[] args) { Product lc = new Product(); ProductModel rc = new ProductModel(); rc.Title = "abc"; bool modified = SetIfModified(l => l.Title, lc, r.Title); // modified is true // lc.Title is "abc" }


Dado que lo más probable es que no desee escribir el código para cada entidad y par de modelo, debe confiar en la reflexión para mapear las propiedades que son las mismas entre la entidad y el modelo y luego simplemente comparar esos valores con los Infos de la propiedad.

Editar: Copia agregada del valor modificado y bloqueo para varios hilos.

class Program { static void Main(string[] args) { if (ModifyExistingEntity(new Product { Name = "bar" }, new ProductModel { Name = "test" })) Console.WriteLine("Product modified"); if (ModifyExistingEntity(new Customer { Number = 1001 }, new CustomerModel { Number = 1002 })) Console.WriteLine("Customer was modified"); if (!ModifyExistingEntity(new Customer { Number = 1001 }, new CustomerModel { Number = 1001 })) Console.WriteLine("Customer was not modified"); Console.ReadKey(); } protected static bool ModifyExistingEntity<TEntity, TModel>(TEntity entity, TModel model) { var isModified = false; GetProperties(entity, model).ForEach( propertyInfo => { var item2Value = propertyInfo.Item2.GetValue(model, null); if (Equals(propertyInfo.Item1.GetValue(entity, null), item2Value)) return; propertyInfo.Item1.SetValue(entity, item2Value); isModified = true; }); return isModified; } private static readonly object MappingLock = new object(); private static readonly Dictionary<Tuple<Type, Type>, List<Tuple<PropertyInfo, PropertyInfo>>> Mapping = new Dictionary<Tuple<Type, Type>, List<Tuple<PropertyInfo, PropertyInfo>>>(); protected static List<Tuple<PropertyInfo, PropertyInfo>> GetProperties<TEntity, TModel>(TEntity entity, TModel model) { lock (MappingLock) { var key = new Tuple<Type, Type>(typeof (TEntity), typeof (TModel)); if (Mapping.ContainsKey(key)) { return Mapping[key]; } var modelProperties = typeof (TModel).GetProperties(); var newMapping = (from propertyInfo in typeof (TEntity).GetProperties() let modelPropertyInfo = modelProperties.SingleOrDefault(mp => mp.Name == propertyInfo.Name) select new Tuple<PropertyInfo, PropertyInfo>(propertyInfo, modelPropertyInfo)) .ToList(); Mapping.Add(key, newMapping); return newMapping; } } }


Esta es una situación donde el uso de macro es apropiado:

#define CheckAndAssign(dst,src) (dst != src && (dst = src, true)) return ( CheckAndAssign (entity.Title, item.Title) | CheckAndAssign (entity.ServerId, item.Id)); #undef CheckAndAssign

Bueno, siempre que el lenguaje sea C, C ++, Objective-C o cualquier otra cosa con macros. Espero que en C ++ puedas convertirlo en una plantilla de alguna manera.


He visto las respuestas más intrincadas a esta pregunta, pero creo que sería más adecuado con una solución simple bastante directa y sin sentido.

Supongo que está utilizando algún tipo de patrón de Data Mapper en su base de código y Product es su entidad DAL / Domain y ProductModel es su objeto a nivel de aplicación. En ese caso, simplemente tendría un método que compara los dos (que luego se pueden mover a una capa separada), y si no son iguales, mapea.

Pero esto plantea la pregunta, ¿por qué te preocupa actualizar solo si ha cambiado? Probablemente sea aceptable simplemente actualizar cada vez.

Además, probablemente no deberías pasar una entidad a un método con la expectativa de que se actualice.

Cambiaría la lógica de la siguiente manera:

protected bool UpdateIfChanged(Product entity, ProductModel item) { var areEqual = CompareProductAndProductModel(entity, item); if(!areEqual) UpdateProduct(MapProductModelToProduct(item)); return !areEqual; } internal bool CompareProductAndProductModel(Product product, ProductModel productModel) { return product.Title == productModel.Title && product.ServerId == productModel.Id; //could be abstracted to an equality comparer if you were inclined }

La salida más grande que esta respuesta hace de otras respuestas es que no modifica la entidad Producto. En su lugar, compara el Product y el ProductModel , pero si se detectan cambios, utiliza el ProductModel para crear un nuevo Product , que luego se pasa a otro método DAL que realmente hace el trabajo de actualización. Creo que este es probablemente el enfoque más fácil de mantener ya que no tiene que lidiar con métodos que cambian el estado de los objetos pasados ​​a ellos (acoplamiento implícito incluso si el método y la persona que llama existen en diferentes lugares), lo que significa que no tiene que realice un seguimiento mental de los cambios de estado en la entidad mientras avanza por el código durante la depuración.


Mira mi propia solución (casi tan extraña como las demás)

[TestMethod] public void DifferentTitleAndId_ExpectModified() { var entity = new Product { Id = 0, ServerId = 0, Title = "entity title" }; var model = new ProductModel { Id = 1, Title = "model title" }; bool isModified = ModifyExistingEntity(entity, model); Assert.IsTrue(isModified); } protected bool ModifyExistingEntity(Product entity, ProductModel model) { return IsModified(entity.Title, model.Title, x => entity.Title = x) | IsModified(entity.ServerId, model.Id, x => entity.ServerId = x); } protected bool IsModified<T>(T value1, T value2, Action<T> setter) { return IsModified(() => value1, () => value2, () => setter(value2)); } protected bool IsModified<T>(Func<T> valueGetter1, Func<T> valueGetter2, Action setter) { if (!Equals(valueGetter1(), valueGetter2())) { setter(); return true; } return false; }


No estoy seguro si la clase de Product es extensible para usted, ni estoy seguro si está buscando una respuesta "elegante" o simplemente una más simple ... pero si es así, puede cambiar las cosas un poco y poner el lógica en la clase de Product sí; en el que terminas con un método bastante legible:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { entity.SetTitle(item.Title); entity.SetServerId(item.Id); return entity.WasModified(); }

La ventaja adicional es que encapsula perfectamente el comportamiento en Product (además de validación, etc.)

public partial class Product { public void SetTitle(string title) { if(this.Title!=title) //and other validation, etc { this.Title = title; Modified(); } } public void SetServerId(int serverId) { if(this.ServerId!=serverId) { this.ServerId=serverID; Modified(); } } private bool _wasModified; private void Modified() { //Or implement INotifyPropertyChanged if you like _wasModified=true; } public bool WasModified() { return _wasModified; } }

Por supuesto, si no necesita ninguna "lógica de negocios" y esto realmente es solo una operación de mapeo no verificada, cualquiera de las respuestas más inteligentes aquí lo hará :)


No hay varita mágica para simplificar esto.

Puede hacer que la entidad misma proporcione una propiedad IsModified, que luego es establecida por los establecedores de propiedades, tales como:

public string Title { get { return _title; } set { if (value != _title) { _title = value; IsModified = true; } } }

Si eso es demasiado trabajo, su solución está bien.


Si desea que sea legible, puede crear una clase para este propósito con un uso muy simple evitando el código repetitivo:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { return new Modifier<Product>(entity) .SetIfNeeded(e => e.Title, item.Title); .SetIfNeeded(e => e.ServerId, item.Id); .EntityWasModified; }

Implementación:

Tomé un código de Patrick Hofman para generar un setter a partir de la expresión getter.

public class Modifier<TEntity> { public Modifier(TEntity entity) { Entity = entity; } public TEntity Entity { get; private set; } public bool EntityWasModified { get; private set; } public Modifier<TEntity> SetIfNeeded<TProperty>(Expression<Func<TEntity, TProperty>> entityPropertyGetter, TProperty modelValue) { var getter = entityPropertyGetter.Compile(); var setter = GetSetterExpression(entityPropertyGetter).Compile(); if (!object.Equals(getter(Entity), modelValue)) { setter(Entity, modelValue); EntityWasModified = true; } return this; } private static Expression<Action<TEntity, TProperty>> GetSetterExpression(Expression<Func<TEntity, TProperty>> getterExpression) { var newValue = Expression.Parameter(getterExpression.Body.Type); return Expression.Lambda<Action<TEntity, TProperty>>( Expression.Assign(getterExpression.Body, newValue), getterExpression.Parameters[0], newValue); } }

Es posible que desee almacenar en caché el resultado de .Compile para mejorar el rendimiento.


Si solo quiere líneas de código más cortas, puede compactarlo a las asignaciones de C-Style:

ModifyExistingEntity( Product entity, ProductModel item ) { bool isModified = false; isModified |= ( entity.Title != item.Title ) && retTrue( entity.Title = item.Title ); isModified |= ( entity.ServerId != item.Id ) && retTrue( entity.ServerId = item.Id ); return isModified; } static bool RetTrue<T>(T dummy) { return true; } // Helper method.


Tiene un caso de acoplamiento temporal allí, es decir, está mezclando el control de si la entidad ha cambiado con las asignaciones. Si separa los dos, su código se vuelve mucho más limpio:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = this.IsEntityModified(entity, item); if (isModified) { this.UpdateEntity(entity, item); } return isModified; } private bool IsEntityModified(Product entity, ProductModel item) { return entity.Title != item.Title || entity.ServerId != item.ServerId; } private void UpdateEntity(Product entity, ProductModel item) { entity.Title = item.Title; entity.ServerId = item.Id; }

Hacer cualquier cosa inteligente y funky (TM) con Func<> o algo así, no parece ser útil en este caso, ya que no transportaría su intención tan claramente.