【问题标题】:WPF MarkupExtension to bind to a DictionaryWPF MarkupExtension 绑定到字典
【发布时间】:2020-12-25 16:49:16
【问题描述】:

我有一个应用程序对绑定到自定义FrameworkElements 的数据使用稍作修改的ObservableConcurrentDictionary(为ConcurrentDictionary 比较器公开的计数和添加的构造函数)。字典中存储的数据有很多属性需要显示或者影响渲染。

public class DictionaryData : ObservableConcurrentDictionary<string, ItemValueData> 
{ public DictionaryData() : base(StringComparer.InvariantCultureIgnoreCase) { } }

public class ItemValueData
{
    // properties
    public string Source { get; set; }
    public string Name   { get; set; }
    public int Quality   { get; set; }
    public double Value  { get; set; }
    // ... many other properties
    // omitted members / constructors / private variable etc.
}

ObservableConcurrentDictionary 数据被实例化为 DD a DependencyProperty 的 Window/Canvas/Page/Container...

public DictionaryData DD {
    get => (DictionaryData)GetValue(DDProperty); 
    set { SetValue(DDProperty, value); OnPropertyChanged("DDProperty"); }
}

public readonly DependencyProperty DDProperty =
    DependencyProperty.Register("DD", typeof(DictionaryData), typeof(MyWindowApp)
    , new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));

在有效的 XAML 中,我目前正在为 ItemValueData 类中的每个唯一属性使用不同的绑定转换器。

<ElementA Value="{av:Binding DD Converter={Converters:ItemConverterName}
                 , ConverterParameter='Item001Name'}" .../>
<ElementB Value="{av:Binding DD Converter=Converters:ItemConverterQuality}
                 , ConverterParameter='Item001Name'}" .../>
<ElementC Value="{av:Binding DD Converter=Converters:ItemConverterValue}
                 , ConverterParameter='Item001Name'}" .../>
<ElementD Value="{av:Binding DD Converter=Converters:ItemConverterSource}
                 ,ConverterParameter='Item001Name'}" .../>
<!-- several hundred FrameWorkElements -->
<ElementA Value="{av:Binding DD Converter={Converters:ItemConverterValue}
                 , ConverterParameter='Item400Name'}" .../>

每个转换器处理 ItemValueData 类的单个属性。 (.Name 映射到 ItemConverterName 等等...)

我想要的是一个转换器,它将通过传入要转换的属性的名称以及查找数据的字典的键来转换任何属性。

[ValueConversion(typeof(DictionaryData), typeof(object))]
public class ItemConverterGeneric : MarkupExtension, IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {    
        try {
            if (value != null && parameter != null) {
                DictionaryData dict = (DictionaryData)value;
                // Would like make the property use a class here 
                // SomeClass x = parameter as SomeClass;
                // string key = x.Key;
                // string prop = x.Prop;
                string key = parameter as string;
                if (dict.ContainsKey(key)) { 
                   // switch(prop) { pick the right property }
                   return dict[key].Name; // pass as parameter?
                } 
            }
            return Binding.DoNothing;
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return Binding.DoNothing;
        }
    }

    public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
    { return DependencyProperty.UnsetValue; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    { return this; }
}

我见过使用数组传递多个参数的答案:multiple parameters not truly convertedmultiple parameters order mattersa question asking for two parameters。我见过的没有一个转换器能够使用多个命名参数,其中参数是一个类(无论如何它是一个对象)并且在 XAML 中具有该语法。

<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric}
        , ConverterParameterKey='ItemXName', ConvertProperty='Name', ConvertSource='DatabaseX'}" .../>
<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric}
        , ConverterParameterKey='ItemXName', ConvertProperty='Value', ConvertSource='DatabaseX'}" .../>

使用 IValueConverter/MarkupExtension 的理由

字典是通过处理作为容器源的动态加载的 XAML 的内容来动态构建的。 var tempLoad = XamlReader.Load(Fs); 使用转换器可以解决不存在的键、带有特殊字符的键以及必须依赖字符串解析 Binding b.Path.PathBinding b.ConverterParameter 的内容的问题,因为后者只是 key

当然,其他人已经将字典或表格映射到许多(数百个)单个 FrameworkElement / Control / Custom Elements 并遇到了这个问题......

有没有办法让MarkupExtension 扩展 XAML 语法并转换属性?

【问题讨论】:

  • 还是不清楚你想要什么。也许您应该提供一个真正有意义的连贯示例,并尝试解释它或您的意图。我只阅读转换器和映射,但您从未解释过您正在映射和转换的内容。你在做什么? “我目前正在为 ItemValueData 类中的每个唯一属性使用不同的绑定转换器” 没有任何意义,所以请解释一下。您要求替代解决方案,而在不知道您的意图的情况下很难提出建议。
  • 字典是一个快速查找的集合。它不是作为数据绑定的数据源的最佳选择。但是为什么不直接绑定而不是使用转换器呢?每个属性都有一个转换器实现是出现问题的线索。实现自定义标记扩展非常快,但仍然过于复杂。使用简单的绑定。

标签: c# wpf xaml ivalueconverter markup-extensions


【解决方案1】:

您不应该人为地将其复杂化。只需使用通用数据绑定并使用集合索引器:

Dictionary<string, ItemValueData> Map { get; } = new Dictionary<string, ItemValueData>
{
  { "Key to value1", new ItemValueData() },
  { "Key to value2", new ItemValueData() }
};

<TextBlock Text="{Binding Map[Key to value1].Name}" />    
<TextBlock Text="{Binding Map[Key to value2].Quality}" />

Binding Path Syntax


正如您坚持使用MarkupExtension,我可以为您提供定制的DictBindingExtension
它包装/重新发明了已经提供您需要的一切的默认绑定(参见上面的示例)。
目前还不清楚为什么这不适合你,我会在这里停下来。但是由于我找到了我曾经写过的自定义BindingResolver 的现有类,我将为您提供一个构建在该类之上的简单扩展。您反对使用通用绑定标记扩展的所有论点(在您的使用 IValueConverter/MarkupExtension 部分的理由中)都是不合理的。

您将整个 UI 相关数据存储在 Dictionary 中的方法也是非常错误的。我从未遇到过这样的情况,即我或某人“将字典或表格映射到众多(数百个)单个FrameworkElement/Control/自定义元素”。这样的解决方案如何扩展?
无论您的数据输入是什么,都需要另一个抽象级别来摆脱那些键值对结构化数据。
您通常更喜欢使用视图模型正确构建数据,然后让框架根据数据模板为您动态填充控件。

解决方案 #1

由于MarkupExtension 本身不是动态的,因为它在初始化期间只被调用一次,所以BindingResolver 将挂钩到Binding,例如到Dictionary 并允许在更新原始目标之前对该值应用过滤器,例如TextBlock.Text 带有转换/过滤的值。它基本上是一个(可选)值转换器的封装,允许自定义MarkupExtension 接受动态Binding
或者通过StaticResource设置扩展的DictBindingExtension.Source属性以摆脱绑定功能。

用法

<TextBox Text="{local:DictBind {Binding DictionaryData}, Key=Key to value2, ValuePropertyName=Quality}" />

DictBindingExtension.cs

class DictBindExtension : MarkupExtension
{
  public object Source { get; }
  public object Key { get; set; }
  public string ValuePropertyName { get; set; }

  public DictBindExtension(object source)
  {
    this.Source = source;
    this.Key = null;
    this.ValuePropertyName = string.Empty;
  }

  #region Overrides of MarkupExtension

  /// <inheritdoc />
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    IDictionary sourceDictionary = null;
    switch (this.Source)
    {
      case IDictionary dictionary:
        sourceDictionary = dictionary;
        break;
      case BindingBase binding:
        var provideValueTargetService =
          serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        object targetObject = provideValueTargetService?.TargetObject;
        if (targetObject == null)
        {
          return this;
        }

        var bindingResolver = new BindingResolver(
          targetObject as FrameworkElement,
          provideValueTargetService.TargetProperty as DependencyProperty)
        {
          ResolvedSourceValueFilter = value => GetValueFromDictionary(value as IDictionary)
        };

        var filteredBinding = bindingResolver.ResolveBinding(binding as Binding) as BindingBase;
        return filteredBinding?.ProvideValue(serviceProvider);
      case MarkupExtension markup:
        sourceDictionary = markup.ProvideValue(serviceProvider) as IDictionary;
        break;
    }


    return GetValueFromDictionary(sourceDictionary);
  }

  private object GetValueFromDictionary(IDictionary sourceDictionary)
  {
    if (sourceDictionary == null)
    {
      throw new ArgumentNullException(nameof(sourceDictionary), "No source specified");
    }

    object value = sourceDictionary[this.Key];
    PropertyInfo propertyInfo = value?.GetType().GetProperty(this.ValuePropertyName);
    return propertyInfo == null ? null : propertyInfo.GetValue(value);
  }

  #endregion
}

BindingResolver.cs

class BindingResolver : FrameworkElement
{
  #region ResolvedValue attached property

  public static readonly DependencyProperty ResolvedValueProperty = DependencyProperty.RegisterAttached(
    "ResolvedValue", typeof(object), typeof(BindingResolver), new PropertyMetadata(default(object), BindingResolver.OnResolvedValueChanged));

  public static void SetResolvedValue(DependencyObject attachingElement, object value) => attachingElement.SetValue(BindingResolver.ResolvedValueProperty, value);

  public static object GetResolvedValue(DependencyObject attachingElement) => (object)attachingElement.GetValue(BindingResolver.ResolvedValueProperty);

  #endregion ResolvedValue attached property

  public DependencyProperty TargetProperty { get; set; }
  public WeakReference<DependencyObject> Target { get; set; }
  public WeakReference<Binding> OriginalBinding { get; set; }
  public Func<object, object> ResolvedSourceValueFilter { get; set; }
  public Func<object, object> ResolvedTargetValueFilter { get; set; }
  private bool IsUpDating { get; set; }
  private static ConditionalWeakTable<DependencyObject, BindingResolver> BindingTargetToBindingResolversMap { get; } = new ConditionalWeakTable<DependencyObject, BindingResolver>();

  public BindingResolver(DependencyObject target, DependencyProperty targetProperty)
  {
    if (target == null)
    {
      throw new ArgumentNullException(nameof(target));
    }
    if (targetProperty == null)
    {
      throw new ArgumentNullException(nameof(targetProperty));
    }
    this.Target = new WeakReference<DependencyObject>(target);
    this.TargetProperty = targetProperty;
  }

  private void AddBindingTargetToLookupTable(DependencyObject target) => BindingResolver.BindingTargetToBindingResolversMap.Add(target, this);

  public object ResolveBinding(Binding bindingExpression)
  {
    if (!this.Target.TryGetTarget(out DependencyObject bindingTarget))
    {
      throw new InvalidOperationException("Unable to resolve sourceBinding. Binding target is 'null', because the reference has already been garbage collected.");
    }

    AddBindingTargetToLookupTable(bindingTarget);

    Binding binding = bindingExpression;
    this.OriginalBinding = new WeakReference<Binding>(binding);

    // Listen to data source
    Binding sourceBinding = CloneBinding(binding);
    BindingOperations.SetBinding(
      bindingTarget,
      BindingResolver.ResolvedValueProperty,
      sourceBinding);

    // Delegate data source value to original target of the original Binding
    Binding targetBinding = CloneBinding(binding, this);
    targetBinding.Path = new PropertyPath(BindingResolver.ResolvedValueProperty);

    return targetBinding;
  }

  private Binding CloneBinding(Binding binding)
  {
    Binding clonedBinding;
    if (!string.IsNullOrWhiteSpace(binding.ElementName))
    {
      clonedBinding = CloneBinding(binding, binding.ElementName);
    }
    else if (binding.Source != null)
    {
      clonedBinding = CloneBinding(binding, binding.Source);
    }
    else if (binding.RelativeSource != null)
    {
      clonedBinding = CloneBinding(binding, binding.RelativeSource);
    }
    else
    {
      clonedBinding = CloneBindingWithoutSource(binding);
    }

    return clonedBinding;
  }

  private Binding CloneBinding(Binding binding, object bindingSource)
  {
    Binding clonedBinding = CloneBindingWithoutSource(binding);
    clonedBinding.Source = bindingSource;
    return clonedBinding;
  }

  private Binding CloneBinding(Binding binding, RelativeSource relativeSource)
  {
    Binding clonedBinding = CloneBindingWithoutSource(binding);
    clonedBinding.RelativeSource = relativeSource;
    return clonedBinding;
  }

  private Binding CloneBinding(Binding binding, string elementName)
  {
    Binding clonedBinding = CloneBindingWithoutSource(binding);
    clonedBinding.ElementName = elementName;
    return clonedBinding;
  }

  private MultiBinding CloneBinding(MultiBinding binding)
  {
    IEnumerable<BindingBase> bindings = binding.Bindings;
    MultiBinding clonedBinding = CloneBindingWithoutSource(binding);
    bindings.ToList().ForEach(clonedBinding.Bindings.Add);
    return clonedBinding;
  }

  private PriorityBinding CloneBinding(PriorityBinding binding)
  {
    IEnumerable<BindingBase> bindings = binding.Bindings;
    PriorityBinding clonedBinding = CloneBindingWithoutSource(binding);
    bindings.ToList().ForEach(clonedBinding.Bindings.Add);
    return clonedBinding;
  }

  private TBinding CloneBindingWithoutSource<TBinding>(TBinding sourceBinding) where TBinding : BindingBase, new()
  {
    var clonedBinding = new TBinding();
    switch (sourceBinding)
    {
      case Binding binding:
        {
          var newBinding = clonedBinding as Binding;
          newBinding.AsyncState = binding.AsyncState;
          newBinding.BindingGroupName = binding.BindingGroupName;
          newBinding.BindsDirectlyToSource = binding.BindsDirectlyToSource;
          newBinding.Converter = binding.Converter;
          newBinding.ConverterCulture = binding.ConverterCulture;
          newBinding.ConverterParameter = binding.ConverterParameter;
          newBinding.FallbackValue = binding.FallbackValue;
          newBinding.IsAsync = binding.IsAsync;
          newBinding.Mode = binding.Mode;
          newBinding.NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated;
          newBinding.NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated;
          newBinding.NotifyOnValidationError = binding.NotifyOnValidationError;
          newBinding.Path = binding.Path;
          newBinding.StringFormat = binding.StringFormat;
          newBinding.TargetNullValue = binding.TargetNullValue;
          newBinding.UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter;
          newBinding.UpdateSourceTrigger = binding.UpdateSourceTrigger;
          newBinding.ValidatesOnDataErrors = binding.ValidatesOnDataErrors;
          newBinding.ValidatesOnExceptions = binding.ValidatesOnExceptions;
          newBinding.XPath = binding.XPath;
          newBinding.Delay = binding.Delay;
          newBinding.ValidatesOnNotifyDataErrors = binding.ValidatesOnNotifyDataErrors;
          binding.ValidationRules.ToList().ForEach(newBinding.ValidationRules.Add);
          break;
        }
      case PriorityBinding priorityBinding:
        {
          var newBinding = clonedBinding as PriorityBinding;
          newBinding.BindingGroupName = priorityBinding.BindingGroupName;
          newBinding.FallbackValue = priorityBinding.FallbackValue;
          newBinding.StringFormat = priorityBinding.StringFormat;
          newBinding.TargetNullValue = priorityBinding.TargetNullValue;
          newBinding.Delay = priorityBinding.Delay;
          break;
        }
      case MultiBinding multiBinding:
        {
          var newBinding = clonedBinding as MultiBinding;
          newBinding.BindingGroupName = multiBinding.BindingGroupName;
          newBinding.Converter = multiBinding.Converter;
          newBinding.ConverterCulture = multiBinding.ConverterCulture;
          newBinding.ConverterParameter = multiBinding.ConverterParameter;
          newBinding.FallbackValue = multiBinding.FallbackValue;
          newBinding.Mode = multiBinding.Mode;
          newBinding.NotifyOnSourceUpdated = multiBinding.NotifyOnSourceUpdated;
          newBinding.NotifyOnTargetUpdated = multiBinding.NotifyOnTargetUpdated;
          newBinding.NotifyOnValidationError = multiBinding.NotifyOnValidationError;
          newBinding.StringFormat = multiBinding.StringFormat;
          newBinding.TargetNullValue = multiBinding.TargetNullValue;
          newBinding.UpdateSourceExceptionFilter = multiBinding.UpdateSourceExceptionFilter;
          newBinding.UpdateSourceTrigger = multiBinding.UpdateSourceTrigger;
          newBinding.ValidatesOnDataErrors = multiBinding.ValidatesOnDataErrors;
          newBinding.ValidatesOnExceptions = multiBinding.ValidatesOnExceptions;
          newBinding.Delay = multiBinding.Delay;
          newBinding.ValidatesOnNotifyDataErrors = multiBinding.ValidatesOnNotifyDataErrors;
          multiBinding.ValidationRules.ToList().ForEach(newBinding.ValidationRules.Add);
          break;
        }
      default: return null;
    }

    return clonedBinding;
  }

  private static void OnResolvedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    if (d is BindingResolver bindingResolver)
    {
      if (bindingResolver.IsUpDating)
      {
        return;
      }

      bindingResolver.IsUpDating = true;
      bindingResolver.UpdateSource();
      bindingResolver.IsUpDating = false;
    }
    else
    {
      if (BindingResolver.BindingTargetToBindingResolversMap.TryGetValue(d, out bindingResolver))
      {
        if (bindingResolver.IsUpDating)
        {
          return;
        }

        bindingResolver.IsUpDating = true;
        bindingResolver.UpdateTarget();
        bindingResolver.IsUpDating = false;
      }
    }
  }

  private static bool TryClearBindings(DependencyObject bindingTarget, BindingResolver bindingResolver)
  {
    if (bindingTarget == null)
    {
      return false;
    }

    Binding binding = BindingOperations.GetBinding(bindingTarget, bindingResolver.TargetProperty);
    if (binding != null && binding.Mode == BindingMode.OneTime)
    {
      BindingOperations.ClearBinding(bindingTarget, BindingResolver.ResolvedValueProperty);
      BindingOperations.ClearBinding(bindingTarget, bindingResolver.TargetProperty);
    }

    return true;
  }

  private void UpdateTarget()
  {
    if (!this.Target.TryGetTarget(out DependencyObject target))
    {
      return;
    }

    object resolvedValue = BindingResolver.GetResolvedValue(target);
    object value = this.ResolvedSourceValueFilter.Invoke(resolvedValue);

    BindingResolver.SetResolvedValue(this,value);
  }

  private void UpdateSource()
  {
    if (!this.Target.TryGetTarget(out DependencyObject target))
    {
      return;
    }

    object resolvedValue = BindingResolver.GetResolvedValue(this);
    object value = this.ResolvedTargetValueFilter.Invoke(resolvedValue);

    BindingResolver.SetResolvedValue(target, value);
  }
}

解决方案 #2

将相关属性添加到您的 IValueConverter 实现中:

用法

<TextBox>
  <TextBox.Text>
    <Binding Path="DictionaryData">
      <Binding.Converter>
        <ItemConverterGeneric Key="Key to value2" PropertyName="Quality" />
      </Binding.Converter>
    </Binding>
  </TextBox.Text>
</TextBox>

ItemConverterGeneric.cs

public class ItemConverterGeneric : IValueConverter 
{
    public object Key { get; set; }
    public object ValuePropertyName { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    {    
            if (!(value is Dictionary<string, object> dict) || Key == null || ValuePropertyName == null) 
            {
                return Binding.DoNothing;
            }
            string key = Key as string;
            if (dict.TryGetValue(key, out object dataItem)) 
            { 
                // Use reflection to pick the right property of dataItem                  
            } 
    }

    public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException();
}

【讨论】:

  • 那你为什么不做呢,是什么原因呢?
  • 我认为你的任何理由都没有道理。 “字典是动态构建的……” 您设置的绑定和转换器不是动态的。您的示例中没有任何内容是动态的。即使使用自定义标记扩展,此扩展的参数也是静态的,在设计时提供。 “使用转换器解决不存在键的问题” 所有键都是硬编码的,因此在实现 UI 时必须提前知道它们,否则绑定将失败。在这种情况下,转换器不会改变任何内容:密钥仍然不存在。
  • “带有特殊字符的键” 为什么要用“特殊字符”作为键?使用转换器时,您必须将密钥指定为 XAML 中的参数。将键指定为集合绑定的索引有什么区别? “不得不依赖字符串解析......”_我没有看到任何字符串解析,也无法在这种情况下提出字符串解析的场景。我明白字符串是关键。
  • 我已从 DictBindingExtension 中删除了默认构造函数,并从 Source 属性中删除了 set 方法,以确保正确初始化(防止错误使用)。
  • 我误解了基于 MarkupExtension / IValueConverter 的类如何将其属性引入 Converter 内的 XAML 而不是 ConverterParameter。此外,在我的情况下,我似乎不需要该参数(尽管我真的很想看一个两者都有的例子。)
【解决方案2】:

虽然提供的答案有一些有趣的可能性,但它们依赖于反射和运行时编译器服务。

我最终在下面想出的不是。

我误解了基于 MarkupExtension / IValueConverter 的类如何将其属性引入 Converter 内的 XAML 而不是 ConverterParameter。此外,在我的情况下,我似乎不需要该参数(尽管我真的很想看到一个使用 Converter 和 ConverterParameter 的示例)。

MarkupExtension/IValueConverter 应该在里面指定了命名属性:

[ValueConversion(typeof(DictionaryData), typeof(object))]
public class ItemConverterGeneric : MarkupExtension, IValueConverter {
    public string Path {get; set;}
    public string Property {get; set;}
    public string Source {get; set;}
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 
        try {
            if !(value == null || string.IsNullorEmpty(Path) || string.IsNullorEmpty(Property) || string.IsNullorEmpty(Source)) {
                DictionaryData dict = value as DictionaryData;
                // These are hard coded, but you could use refelction to make it look nicer 
                // and automatically handle any new properties added to the class.
                if (dict.ContainsKey(Path)) { 
                   switch(Property.ToUpper()) { 
                       case "SOURCE":   return dict[Path].Source;
                       case "NAME":     return dict[Path].Name;
                       case "QUALITY":  return dict[Path].Quality;
                       case "VALUE:     return dict[Path].Value;
                       //... etc and no default: needed as the outer return will handle it.
                   }
                } 
            }
            return Binding.DoNothing;
        } catch (Exception ex) {
            // Console.WriteLine(ex.Message); // or log the error 
            return Binding.DoNothing;
        }
    }

    public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
    { return DependencyProperty.UnsetValue; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    { return this; }
}       

虽然我最初的 XAML 语法很接近,但第一个属性之后的每个属性都应该在 Converter 中指定,用逗号分隔:

<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric
   Path='ItemXName001', Property='Value', Source='DataSourceX'}}" .../>
<!--  hundreds of DataSourceX bound items -->
<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric
   Path='ItemXName401', Property='Value', Source='DataSourceX'}}" .../>
<ElementY Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric
   Path='ItemYName001', Property='Value', Source='DataSourceY'}}" .../>
<!--  hundreds of DataSourceY bound items -->
<ElementY Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric
   Path='ItemYName401', Property='Value', Source='DataSourceY'}}" .../>

【讨论】:

  • 我必须指出,您提供的解决方案确实不需要反射,但现在在可扩展性方面已成为一场噩梦。我选择反射的原因是它显着提高了可扩展性/可维护性。像您一样使用switch,需要在添加或重命名新属性时更新代码。这个switch 将不断增长,不断增长,最终失去控制。 switch 的扩展性总是很差,这就是为什么你通常会避免这样的声明(当然同样适用于链式 if)。
  • 您不会打开可能更改的对象或可能添加对象的对象(类型、属性等)。我总是更喜欢反射而不是爆炸的switch。在这种情况下使用反射并不过分。
猜你喜欢
  • 1970-01-01
  • 2011-01-23
  • 2016-11-20
  • 2012-09-26
  • 2011-03-27
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
  • 2014-10-09
相关资源
最近更新 更多