Microsoft는 INotifyPropertyChanged
자동 속성에서와 같이 딱딱한 것을 구현 해야합니다.{get; set; notify;}
한다고 생각합니다. 아니면 합병증이 있습니까?
우리는 우리 자신의 속성에 ‘알림’과 같은 것을 구현할 수 있습니까? INotifyPropertyChanged
수업 에 구현할 수있는 우아한 솔루션이 있습니까? 아니면 그것을 수행하는 유일한 방법은PropertyChanged
각 속성 이벤트를 입니다.
그렇지 않다면 PropertyChanged
이벤트 를 발생시키기 위해 코드를 자동 생성하는 무언가를 작성할 수 있습니까?
답변
postsharp와 같은 것을 사용하지 않고 사용하는 최소 버전은 다음과 같은 것을 사용합니다.
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
각 속성은 다음과 같습니다.
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
크지 않은; 원하는 경우 기본 클래스로 사용할 수도 있습니다. 에서 bool
반환 SetField
하면 다른 논리를 적용하려는 경우 비 작동인지 알려줍니다.
또는 C # 5를 사용하면 더 쉽습니다.
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}
다음과 같이 호출 할 수 있습니다 :
set { SetField(ref name, value); }
컴파일러가 "Name"
자동으로 추가합니다 .
C # 6.0을 사용하면 구현이 더 쉬워집니다.
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
… 그리고 이제 C # 7로 :
protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
답변
.Net 4.5부터는이를 수행하는 쉬운 방법이 있습니다.
.Net 4.5에는 새로운 발신자 정보 속성이 도입되었습니다.
private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
// make sure only to call this if the value actually changes
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(caller));
}
}
함수에 비교자를 추가하는 것이 좋습니다.
EqualityComparer<T>.Default.Equals
답변
Marc의 솔루션은 정말 마음에 들지만 리팩토링을 지원하지 않는 “매직 문자열”을 사용하지 않도록 약간 개선 할 수 있다고 생각합니다. 속성 이름을 문자열로 사용하는 대신 람다 식으로 만들 수 있습니다.
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
Marc의 코드에 다음 방법을 추가하면 트릭을 수행합니다.
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
BTW, 이것은 블로그 게시물 업데이트 URL 에서 영감을
답변
PropertyChanged 추가 기능 이있는 Fody 도 있습니다.
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
}
… 컴파일시 속성 변경 알림을 삽입합니다.
답변
사람들은 성능에 조금 더주의를 기울여야한다고 생각합니다. 바인딩 할 개체가 많거나 (행이 10,000 개 이상인 그리드를 생각할 때) 개체의 값이 자주 변경되는 경우 (실시간 모니터링 앱) 실제로 UI에 영향을줍니다.
나는 여기와 다른 곳에서 발견 된 다양한 구현을 취하고 비교를했다. INotifyPropertyChanged 구현의 성능 비교를 확인하십시오 .
결과를 엿볼 수 있습니다.
답변
내 블로그 ( http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/) 에서 Bindable 클래스를 소개합니다. Bindable
은 사전을 속성 백으로 사용합니다. ref 매개 변수를 사용하여 자체 하위 필드를 관리하기 위해 서브 클래스에 필요한 오버로드를 추가하기가 쉽습니다.
- 마술 줄 없음
- 반사 없음
- 기본 사전 조회를 억제하도록 개선 할 수 있습니다
코드:
public class Bindable : INotifyPropertyChanged {
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null) {
Debug.Assert(name != null, "name != null");
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
/// <remarks>Use this overload when implicitly naming the property</remarks>
protected void Set<T>(T value, [CallerMemberName] string name = null) {
Debug.Assert(name != null, "name != null");
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
다음과 같이 사용할 수 있습니다 :
public class Contact : Bindable {
public string FirstName {
get { return Get<string>(); }
set { Set(value); }
}
}
답변
실제로이 작업을 직접 시도 할 기회는 없었지만 다음에 INotifyPropertyChanged에 대한 큰 요구 사항으로 프로젝트를 설정할 때 컴파일 타임에 코드를 주입 하는 Postsharp 속성을 작성하려고합니다 . 다음과 같은 것 :
[NotifiesChange]
public string FirstName { get; set; }
될 것입니다:
private string _firstName;
public string FirstName
{
get { return _firstname; }
set
{
if (_firstname != value)
{
_firstname = value;
OnPropertyChanged("FirstName")
}
}
}
이것이 실제로 작동하는지 확실하지 않으며 앉아서 시도해야하지만 왜 그런지 모르겠습니다. 하나 이상의 OnPropertyChanged를 트리거해야하는 상황에 대해 일부 매개 변수를 수락해야 할 수도 있습니다 (예를 들어 위 클래스에 FullName 속성이있는 경우).
현재 Resharper에서 사용자 정의 템플릿을 사용하고 있지만 그로 인해 모든 속성이 너무 길어지고 있습니다.
아, 빠른 Google 검색 (이 글을 작성하기 전에 수행해야했던 것)은 적어도 한 사람이 여기 전에 이와 같은 작업을 수행했음을 보여줍니다 . 내가 생각한 바가 아니라 이론이 훌륭하다는 것을 보여줄만큼 충분히 가까웠다.