具有相同访问器的属性

问题描述 投票:0回答:1

我们有一些大型类,其中包含数十个属性,这些属性具有基本相同的简单访问器:

public struct ObservableField<T>
{
  internal T Val;
  public ObservableFile(T val) => this.Val ) val;
  ...
}

public class DataConfiguration
{
  private ObservableField<bool> isEnabled;
  public bool IsEnabled
  {
    get => GetValue( ref isEnabled, nameof(IsEnabled));
    set
    {
      if(SetValue( ref isEnabled, nameof(IsEnabled), value))
        OnDataChanged(nameof(IsEnabled));
    }
  }

  private ObservableField<WrapMode> dataWrapMode;
  public bool DataWrapMode
  {
    get => GetValue( ref dataWrapMode, nameof(DataWrapMode));
    set
    {
      if(SetValue( ref dataWrapMode, nameof(DataWrapMode), value))
        OnDataChanged(nameof(DataWrapMode));
    }
  }

  ...
}

我们想要更改此设置的原因是为了使其更加简洁且不易出错,因为代码库早于 C#4。所以没有

nameof
并且出现了复制粘贴错误:

  private ObservableField<bool> dirty;
  public bool Dirty
  {
    get => GetValue( ref dirty, "Dirty");
    set
    {
      if(SetValue( ref dirty, "Dirty", value))
        OnDataChanged(**"IsEnabled"**);
    }
  }

我们正在寻找最好具有 T4 代码生成的解决方案。目前无法更改

ObservableField
,因为它在较大的代码库中普遍存在,并且该类型的某些属性需要不同的处理。 我也无法对其使用包装器,因为属性和反射有很多魔力,可以发现 GUI 中显示的属性。

使用 C 风格的宏会很简单(但不漂亮或不惯用):

#define OBSERVABLE_WITH_ACCESSORS(name,type) \
  private ObservableField<type> _##name; \
  public ##type ##name \
  { \
    get => GetValue( ref _##name, nameof(##name)); \
    set \
    { \
      if(SetValue( ref _##name, nameof(##name), value)) \
        OnDataChanged(nameof(##name)); \
    } \
  }

这如何在最好的 c#-8.0 中完成? 如果在 C#-8.0 中无法轻松完成,建议使用更新的标准也可以,因为我们确实打算在将来进行迁移。

c# generics properties c#-8.0
1个回答
1
投票

您可以采取一些措施来减少样板文件/复制粘贴错误,而无需使用 T4/源生成器:

  1. 使用
    CallerMemberNameAttribute
    而不是硬编码名称
  2. 无需单独的
    OnDataChanged
    通话

我不知道你的

GetValue
/
SetValue
目前是如何定义的,但请考虑以下内容:

private T GetValue<T>(ref ObservableField<T> field, [CallerMemberName] string propertyName? = null)
{
    // What your GetValue currently does, using propertyName
}

private void SetValue<T>(ref ObservableField<T> field, T value, [CallerMemberName] string propertyName? = null
{
    if (/* What your SetValue currently does */)
        OnDataChanged(propertyName);
}

用法如下:

  private ObservableField<bool> isEnabled;
  public bool IsEnabled
  {
    get => GetValue(ref isEnabled);
    set => SetValue(ref isEnabled, value);
  }

对于 T4 生成,您需要在程序集的编译版本上使用反射,以便生成要添加到该程序集的代码,这有点像先有鸡还是先有蛋的情况。

C# 的源生成器将是一个完美的选择,因为它们能够生成代码以在编译时注入到程序集中,并且它们可以访问程序集中的类型。

它们的学习曲线有点陡峭,从这里这里开始。

当它们第一次出现时,

INotifyPropertyChanged
有很多相当简单的示例,给定一个仅包含支持字段定义的类生成属性(这实际上就是您正在做的事情)。但是,请注意,旧的
ISourceGenerator
接口已被弃用,取而代之的是
IIncrementalGenerator
,并且大多数旧示例都使用
ISourceGenerator


另请注意,您使用

ref
可能 是不必要的。这可以让你做:

public T GetData<T>(ref ObservableField<T> field, string propertyName, T value)
{
    field = new ObservableField<T>(...);
}

因此将新的

ObservableField
实例分配给类的字段。我猜这不是你正在做的事情!

© www.soinside.com 2019 - 2024. All rights reserved.