c# - sinonimo - refactory
Cómo hacer seguro el tipo de enlace de datos y admitir la refactorización (7)
Cuando deseo vincular un control a una propiedad de mi objeto, debo proporcionar el nombre de la propiedad como una cadena. Esto no es muy bueno porque:
- Si la propiedad se elimina o renombra, no obtengo una advertencia del compilador.
- Si cambia el nombre de la propiedad con una herramienta de refactorización, es probable que el enlace de datos no se actualice.
- No obtengo un error hasta el tiempo de ejecución si el tipo de la propiedad es incorrecta, por ejemplo, unir un entero a un selector de fecha.
¿Existe un patrón de diseño que lo evite, pero que todavía tiene la facilidad de uso del enlace de datos?
(Este es un problema en WinForm, Asp.net y WPF y lo más probable es que muchos otros sistemas)
Ahora he encontrado " soluciones para el operador nameof () en C #: typesafe databinding " que también tiene un buen punto de partida para una solución.
Si está dispuesto a utilizar un postprocesador después de compilar su código, notifypropertyweaver .
¿Alguien sabe de una buena solución para WPF cuando los enlaces se hacen en XML en lugar de C #?
1.Si la propiedad se elimina o renombra, no obtengo una advertencia del compilador.
2. Si se cambia el nombre de la propiedad con una herramienta de refactorización, es probable que el enlace de datos no se actualice.
3. No obtengo un error hasta el tiempo de ejecución si el tipo de la propiedad es incorrecta, por ejemplo, unir un entero a un selector de fecha.
Sí, Ian, esos son exactamente los problemas con el enlace de datos provocado por cadenas de caracteres. Usted pidió un patrón de diseño. Diseñé el patrón de modelo de vista segura de tipo (TVM) que es una concreción de la parte del modelo de vista del patrón Model-View-ViewModel (MVVM). Se basa en un enlace de tipo seguro, similar a su propia respuesta. Acabo de publicar una solución para WPF:
http://www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM
El operador nameof se implementó en C # 6.0 con .NET 4.6 y VS2015 en julio de 2015. Lo siguiente sigue siendo válido para C # <6.0
Para evitar cadenas que contengan nombres de propiedad, he escrito una clase simple usando árboles de expresiones para devolver el nombre del miembro:
using System;
using System.Linq.Expressions;
using System.Reflection;
public static class Member
{
private static string GetMemberName(Expression expression)
{
switch (expression.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
var supername = GetMemberName(memberExpression.Expression);
if (String.IsNullOrEmpty(supername))
return memberExpression.Member.Name;
return String.Concat(supername, ''.'', memberExpression.Member.Name);
case ExpressionType.Call:
var callExpression = (MethodCallExpression) expression;
return callExpression.Method.Name;
case ExpressionType.Convert:
var unaryExpression = (UnaryExpression) expression;
return GetMemberName(unaryExpression.Operand);
case ExpressionType.Parameter:
return String.Empty;
default:
throw new ArgumentException("The expression is not a member access or method call expression");
}
}
public static string Name<T>(Expression<Func<T, object>> expression)
{
return GetMemberName(expression.Body);
}
public static string Name<T>(Expression<Action<T>> expression)
{
return GetMemberName(expression.Body);
}
}
Puedes usar esta clase de la siguiente manera. Aunque puede usarlo solo en código (por lo tanto, no en XAML), es bastante útil (al menos para mí), pero su código todavía no es seguro. Puede extender el Nombre del método con un segundo argumento de tipo que define el valor de retorno de la función, lo que restringiría el tipo de la propiedad.
var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty"
Hasta ahora no he encontrado nada que solucione el problema de seguridad de tipo de enlace de datos.
Atentamente
El Framework 4.5 nos proporciona el CallerMemberNameAttribute
, que hace innecesario pasar el nombre de la propiedad como una cadena:
private string m_myProperty;
public string MyProperty
{
get { return m_myProperty; }
set
{
m_myProperty = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
// ... do stuff here ...
}
Si está trabajando en Framework 4.0 con KB2468871 instalado, puede instalar el paquete de compatibilidad de Microsoft BCL a través de nuget , que también proporciona este atributo.
Este artículo de blog plantea algunas buenas preguntas sobre el rendimiento de este enfoque . Podría mejorar esas deficiencias convirtiendo la expresión en una cadena como parte de algún tipo de inicialización estática.
La mecánica real podría ser un poco antiestética, pero aún así sería segura para el tipo, y un rendimiento aproximadamente igual al INotifyPropertyChanged crudo.
Algo así como esto:
public class DummyViewModel : ViewModelBase
{
private class DummyViewModelPropertyInfo
{
internal readonly string Dummy;
internal DummyViewModelPropertyInfo(DummyViewModel model)
{
Dummy = BindingHelper.Name(() => model.Dummy);
}
}
private static DummyViewModelPropertyInfo _propertyInfo;
private DummyViewModelPropertyInfo PropertyInfo
{
get { return _propertyInfo ?? (_propertyInfo = new DummyViewModelPropertyInfo(this)); }
}
private string _dummyProperty;
public string Dummy
{
get
{
return this._dummyProperty;
}
set
{
this._dummyProperty = value;
OnPropertyChanged(PropertyInfo.Dummy);
}
}
}
Gracias a Oliver por haberme puesto en marcha ahora tengo una solución que permite la refactorización y es segura. También me permite implementar INotifyPropertyChanged para que haga frente a las propiedades que se renombran.
Su uso se ve así:
checkBoxCanEdit.Bind(c => c.Checked, person, p => p.UserCanEdit);
textBoxName.BindEnabled(person, p => p.UserCanEdit);
checkBoxEmployed.BindEnabled(person, p => p.UserCanEdit);
trackBarAge.BindEnabled(person, p => p.UserCanEdit);
textBoxName.Bind(c => c.Text, person, d => d.Name);
checkBoxEmployed.Bind(c => c.Checked, person, d => d.Employed);
trackBarAge.Bind(c => c.Value, person, d => d.Age);
labelName.BindLabelText(person, p => p.Name);
labelEmployed.BindLabelText(person, p => p.Employed);
labelAge.BindLabelText(person, p => p.Age);
La clase de persona muestra cómo se implementó INotifyPropertyChanged de una manera segura (o ver esta respuesta como una manera más que agradable de implementar INotifyPropertyChanged, ActiveSharp - Automatic INotifyPropertyChanged también se ve bien):
public class Person : INotifyPropertyChanged
{
private bool _employed;
public bool Employed
{
get { return _employed; }
set
{
_employed = value;
OnPropertyChanged(() => c.Employed);
}
}
// etc
private void OnPropertyChanged(Expression<Func<object>> property)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(BindingHelper.Name(property)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
La clase de ayuda de enlace de WinForms tiene la carne que hace que todo funcione:
namespace TypeSafeBinding
{
public static class BindingHelper
{
private static string GetMemberName(Expression expression)
{
// The nameof operator was implemented in C# 6.0 with .NET 4.6
// and VS2015 in July 2015.
// The following is still valid for C# < 6.0
switch (expression.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
var supername = GetMemberName(memberExpression.Expression);
if (String.IsNullOrEmpty(supername)) return memberExpression.Member.Name;
return String.Concat(supername, ''.'', memberExpression.Member.Name);
case ExpressionType.Call:
var callExpression = (MethodCallExpression) expression;
return callExpression.Method.Name;
case ExpressionType.Convert:
var unaryExpression = (UnaryExpression) expression;
return GetMemberName(unaryExpression.Operand);
case ExpressionType.Parameter:
case ExpressionType.Constant: //Change
return String.Empty;
default:
throw new ArgumentException("The expression is not a member access or method call expression");
}
}
public static string Name<T, T2>(Expression<Func<T, T2>> expression)
{
return GetMemberName(expression.Body);
}
//NEW
public static string Name<T>(Expression<Func<T>> expression)
{
return GetMemberName(expression.Body);
}
public static void Bind<TC, TD, TP>(this TC control, Expression<Func<TC, TP>> controlProperty, TD dataSource, Expression<Func<TD, TP>> dataMember) where TC : Control
{
control.DataBindings.Add(Name(controlProperty), dataSource, Name(dataMember));
}
public static void BindLabelText<T>(this Label control, T dataObject, Expression<Func<T, object>> dataMember)
{
// as this is way one any type of property is ok
control.DataBindings.Add("Text", dataObject, Name(dataMember));
}
public static void BindEnabled<T>(this Control control, T dataObject, Expression<Func<T, bool>> dataMember)
{
control.Bind(c => c.Enabled, dataObject, dataMember);
}
}
}
Esto hace uso de muchas de las cosas nuevas en C # 3.5 y muestra exactamente lo que es posible. Ahora bien, si solo tuviéramos macros higiénicas, el programador de lisp puede dejar de llamarnos ciudadanos de segunda clase)
Una forma de obtener comentarios si sus enlaces no funcionan es crear un DataTemplate y declarar su DataType como el tipo de ViewModel al que se une. Por ejemplo, si tiene un PersonView y un PersonViewModel, haría lo siguiente:
Declarar un DataTemplate con DataType = PersonViewModel y una clave (p. Ej. PersonTemplate)
Corte todo PersonView xaml y péguelo en la plantilla de datos (que, idealmente, puede estar en la parte superior de PersonView).
3a. Cree un ContentControl y configure ContentTemplate = PersonTemplate y vincule su Contenido al PersonViewModel.
3b. Otra opción es no dar una clave a DataTemplate y no configurar ContentTemplate de ContentControl. En este caso, WPF descubrirá qué DataTemplate usar, ya que sabe a qué tipo de objeto se está vinculando. Buscará en el árbol y encontrará su DataTemplate, y dado que coincide con el tipo de enlace, lo aplicará automáticamente como ContentTemplate.
Terminas con esencialmente la misma vista que antes, pero como mapeaste DataTemplate a un DataType subyacente, herramientas como Resharper pueden darte retroalimentación (a través de identificadores de color - Resharper-Options-Settings-Color Identifiers) para saber si tus enlaces están rotos. o no.
Todavía no recibirá las advertencias del compilador, pero puede verificar visualmente si hay enlaces rotos, lo cual es mejor que tener que revisar hacia adelante y hacia atrás entre su vista y el modelo de vista.
Otra ventaja de esta información adicional que proporciona es que también se puede usar para cambiar el nombre de refactorizaciones. Por lo que recuerdo, Resharper puede cambiar automáticamente el nombre de las vinculaciones en las plantillas de datos tipadas cuando se cambia el nombre de propiedad subyacente del ViewModel y viceversa.
x: bind (también llamado "enlaces de datos compilados") para XAML (aplicación universal) en Windows 10 y Windows Phone 10 puede resolver este problema, consulte https://channel9.msdn.com/Events/Build/2015/3-635
No puedo encontrar los documentos en línea para ello, pero no me he esforzado demasiado, ya que es algo que no usaré durante un tiempo. Sin embargo, esta respuesta debería ser un indicador útil para otras personas.