65.9K
CodeProject 正在变化。 阅读更多。
Home

转换器类 - 将所有数据类型转换集中在一个地方

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (16投票s)

2012年9月17日

CPOL

22分钟阅读

viewsIcon

41458

downloadIcon

388

本文解释了如何创建一个类,该类可以通过允许用户注册自己的转换来处理任何类型的数据类型转换,以及如何使其作为全局和局部解决方案工作,从而使不同的线程可以对相同的数据类型进行不同的转换。

背景

您曾经使用过 Convert 类吗?例如 Convert.ToInt32 或更通用的 Convert.ChangeType 等方法?

您遇到过什么问题吗?

嗯,我必须说我第一次使用它时就遇到了问题(我收到一个 Nullable<int>,而 Convert.ChangeType 没有为此做好准备)。解决方案?嗯,我做了许多不同的解决方案,最终决定编写我认为是最终的解决方案,这也是我写这篇文章的原因。

伪解决方案

在我最初的情况下,我使用 FieldInfo 实例的 FieldType 进行转换,并使用 Convert.ChangeType 方法。当我创建一个 int? 类型(或者如果您喜欢 Nullable<int>)的字段时,我得到了一个异常。

在那最初的时刻,我修改了调用者以检查类型是否是任何可空类型,因此它将泛型参数作为实际类型与 ChangeType 调用一起使用。

不难想到,我很快将所有 Convert.ChangeType 调用替换为类似于 MyConvert.ChangeType 的调用,后者在调用 Convert 类之前进行了可空检查。

但问题并没有就此结束。您尝试过将 int 转换为 Color 或反之吗?

直接调用 Color 类,这很简单。颜色可以将其 Argb 值作为 int 返回,并且可以由 int 创建,但 Convert 类不知道这一点,将这些知识放入 MyConvert 类也不是一个好主意。我会把所有可能的转换都放在那里吗?

伪解决方案 1

我的第一个通用解决方案是使用一个 Dictionary,其中键是输入/输出类型对,值是执行转换的委托。

这效果很好,但在应用程序启动期间,我被迫注册所有可能的转换。

您可能会问:这与直接将所有转换放入 MyConvert 类有什么不同?

嗯,区别在于 MyConvert 类可以在通用程序集中创建,可以由该通用程序集使用,并且所有需要的转换都可以由应用程序添加,而不是由库添加,使用在编译库时未知的类型。

伪解决方案 2

使用第一个解决方案,我需要在应用程序启动时注册所有转换,甚至是通用的转换。

因此,如果我想将 IEnumerable<int> 转换为 int[],我需要注册一个转换器。如果我想将 IEnumerable<string> 转换为 string[],我需要另一个转换器。

考虑到这些事情变得很常见,我简单地为通用情况添加了一些内置转换。因此,如果我发现它是一种从任何可枚举类型到数组的转换,那么代码就内置在 MyConvert 类中。

所以,是的,这是一个混合。一些通用转换直接在 MyConvert 类中完成,而其他转换则在应用程序级别注册。

伪解决方案 3

我想您已经可以想象了。我添加了从可枚举类型到数组的转换。但是,在另一个应用程序中,我想要转换到列表而不是数组,最终我不得不更改库。

我想将任何 enum 转换为 int,我还将这些通用知识放入了库中。即使许多应用程序可以从这些更改中受益,但不断更改库(或更具体地说,类)并不是一个好主意,我做了我能做的最简单的事情之一:我添加了一个事件,并从 MyConvert 类中删除了执行通用转换的代码。因此,如果找不到直接转换器,则会调用该事件,并在那里可以完成通用转换。简单,不是吗?

通过这个第三个解决方案,我简化了类。它按照单一职责原则工作,可以直接添加直接转换和通用转换,并且无需为不同类型的转换不断更改类。

所以,这可能是最终的解决方案,但对我来说不是。

我仍然不满意的原因

伪解决方案 3 在功能上是完整的,因为它可以在不要求类更改的情况下添加任何类型的转换,无论是通用转换还是特定转换。

但存在一些薄弱环节。第一个是线程安全。

该类不是线程安全的。当然,当所有转换都在应用程序启动期间注册时,这不会成为问题。但是当编写通用库时,我总是为最坏的情况做准备。如果一个线程正在寻找转换器,而另一个线程正在注册一个新的不相关的转换器,会发生什么?

考虑到该类是静态的,它应该是线程安全的。

简单,使用锁。

这是我最初的想法,但当使用该类进行数百万次转换时,加锁是性能杀手。我真的应该使该类线程安全吗?我应该简单地发出警告说该类在注册新转换器时不是线程安全的吗?

这不是一个容易的决定。但是,我遇到了一个新问题。

两个线程想要对相同类型进行不同的转换。事实上,一个线程想要将 bool 转换为 char01,而另一个线程想要转换为 FT

也许您认为这是一个非常特殊的情况,我应该简单地区别对待它,但是执行转换的代码是 ORM 的一部分,因此,对于那种特殊情况不能以不同的方式完成。

有没有权宜之计?

当然。bool 转换器可以使用线程静态变量来了解应该如何进行转换。因此,需要不同转换的线程应该在线程静态变量中设置一个不同的值。

它奏效了,但对我来说还不够好。

换个角度思考

对于另一种问题,我确实需要一个锁,因为不同的值不断被不同的线程添加,但添加的值从未改变。

因此,为了提高性能并避免过度锁定,每个线程都复制了已有的值。当添加新值时,它在本地(没有锁)和全局对象(有锁)中添加。当在本地找不到值时,会加锁在全局对象中搜索它。

但在大多数情况下,相同的值是从本地副本返回的,没有锁。

您能看出这如何激发了我最后的解决方案吗?

最后,实际解决方案

实际解决方案基于全局/局部配置的原则。

全局配置由静态方法和事件组成。放在那里的任何东西都对整个应用程序有效并使用锁。

但是,当请求转换器时,您应该在线程特定的实例上请求它。因此,非静态方法不是线程安全的,但您可以通过请求线程特定的实例来访问它们。

在搜索转换器时,会检查本地字典。如果那里没有转换,则会调用本地 Searching 事件。如果在搜索事件中返回了转换,则会(仅在本地)注册它,以便下次不会再次调用该事件。

如果不是,则使用全局(静态)配置。事实上,它也是相同的模式,寻找已注册的配置,如果找不到,则调用 GlobalSearching 事件,但所有这些都在锁中。如果找到转换器,它会再次在本地注册,因此下次无需在全局配置中搜索它,从而避免了锁。

好的,还有一个小细节。在其他解决方案中,当找不到转换时,会调用事件来执行转换。新解决方案会调用事件以尝试获取转换器。

新方法的优点是您可以获取任何受支持转换的 Converter 委托(无论是直接注册的还是通过 Searching 事件注册的),然后您可以根据需要执行多次,而无需在字典中进行新的查找,也无需额外调用 Converting 事件。因此,如果使用得当,性能甚至优于伪解决方案。

所以,让我们看看优点

     
  • 可以对默认不支持这些转换的第三方类型进行转换。一个简单的例子是 Colorint 之间的转换,Convert 类不支持它们;
  •  
  • 同时,它允许您为第三方类型添加转换,并遵循单一职责原则。毕竟,一个类型不应该关心如何以各种可能的方式转换自身。一个包含应用程序所有转换的类也不是一个好主意。但是一个允许注册所有转换的类,那就没问题;
  •  
  • 每个注册的 Converter 都是一个类型化的委托。因此,考虑到转换器本身的实现不进行装箱,可以避免这种性能损失;
  •  
  • 如果您需要进行相同的转换数千次,您可以只获取委托一次,然后重新执行数千次。这比例如使用相同的目标类型调用 Convert.ChangeType 数千次要快;
  •  
  • 配置可以是全局的、线程本地的,甚至是“上下文”本地的。因此,您可以为特定调用提供完全不同的配置,而不会影响应用程序的其余部分,也无需被调用代码了解转换的更改。

这种方法的缺点

     
  • Converters 类不以任何受支持的转换开始。当然,通过示例您可以看到如何注册最基本的转换,但注册正确的转换和 Searching 委托始终是应用程序的工作;
  •  
  • Searching 事件返回类型化委托可能很棘手。我希望 GenericConverters 文件夹中的类有助于理解如何做到这一点,但仍然,这是一种劣势;
  •  
  • 有些用户可能会觉得奇怪,我们需要获取本地实例来执行转换,而不是简单地从静态方法执行转换;
  •  
  • 这不是该方法的缺点,但此代码是新的,即使我尝试过测试,可能仍然存在一些错误。因此,如果您发现任何错误,请告诉我,我将尽快纠正。

我可以在哪些应用程序中使用 Converters 类?

在任何需要通用数据类型转换的应用程序中。如果您使用 Convert 类但也有一些额外的测试来查看类型是否可空,您可能会喜欢这个类。如果您有一个 WPF 应用程序总是以相同的方式转换相同的类型,您可以使用它。对于 WPF 和 Silverlight,我非常希望将此类设置为默认转换器,但由于我不知道如何做到这一点(我真的认为设置默认转换器是不可能的),我创建了 ConvertExtensionSmartBinding 类。通过它们,您可以进行如下绑定

{Binding Converter={App:Convert}, Path=SomePath}
{App:SmartBinding SomePath}

并且将使用适当的注册转换。更多信息请参见文章末尾。

这是复制品吗?

有两篇文章也讨论了“终极”(或他们所说的通用)转换器。它们是

那么,这是复制品吗?

答案是否定的。我以前做过很多次这种转换器,通常作为 ORM 框架的一部分。但是这些文章确实启发了我,让我把它变成一个完全独立的组件,而前一篇文章也启发了我创建 FromTypeDescriptorConverter 类。

我认为即使我们关注的是同一个问题,方法也大相径庭。选择您觉得更舒服的解决方案。

变更

我刚刚发表了这篇文章,审阅后发现我没有添加一个类来处理字符串到可空类型的转换。在测试时,我注意到 TypeDescriptor 已经做到了这一点,但我仍然想提供那个代码,在此过程中,我对类进行了重大修改。

那么,发生了什么变化?嗯,从这篇文章的这一点开始,一切都是新的,“使用代码”对于未来的用户来说可能是一个非常有趣的点。但在实现中,有以下更改:

  • SearchingGlobalSearching 事件不再声明为 EventHandler<SearchingConverterEventArgs>。它们都声明为 SearchingConverterEventHandler,反过来只接收 args 参数,没有 sender。我注意到在大多数实现中,sender 完全未使用。此外,它没有类型,如果需要则强制进行类型转换。现在,sender 被命名为 Converters 并正确类型化,是 SearchingConverterEventArgs 的一个属性;
  • SearchingConverterEventArgs 也获得了 IsGlobal 属性,可用于识别事件处理程序是由 Searching 调用还是由 GlobalSearching 调用;
  • 即使在进行 GlobalSearching 时,本地 Converters 实例也会放入事件参数中。此外,如果由于某种原因,GlobalSearching 事件返回了一个使用本地信息(例如基于另一个本地处理程序)的 Converter,则可以在 args 上调用 MakeLocal() 方法,这样该 Converter 将不会全局存储,只会在本地存储;
  • 将 UntypedGet 重命名为 CastedGet。

全局转换方法

这可能是我个人的看法,但我不喜欢这种全局方法。我的理由是它很容易成为性能低下的根源。

看看这两个示例

while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = GlobalConvert.ChangeType<Color>(reader.ReadInt32(0));
  result.EndColor = GlobalConvert.ChangeType<Color>(reader.ReadInt32(1));
  yield return result;
}

while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = Converters.LocalInstance.GetConverter<int, Color>()(reader.ReadInt32(0));
  result.EndColor = Converters.LocalInstance.GetConverter<int, Color>()(reader.ReadInt32(1));
  yield return result;
}

看着第一段代码,不知道第二段代码的存在,我会说它是完全正确的。没有重复的代码,也没有我可以预存储以提高速度的东西。

另一方面,如果我看着第二段代码,而不知道第一段代码,我立即会发现有问题。为什么我一遍又一遍地获取本地实例和转换器?如果我简单地在循环之外获取一次转换器,然后一直使用它,不是更好吗?像这样

var intToColorConverter = Converters.LocalInstance.GetConverter<int, Color>();
while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = intToColorConverter(reader.ReadInt32(0));
  result.EndColor = intToColorConverter(reader.ReadInt32(1));
  yield return result;
}

对我来说,这是一种直觉思维。如果你在循环中重复相同的“获取”,而它每次都会返回相同的值,那就把它放在外面。

这就是为什么我不喜欢全局静态方法可以直接进行转换的原因。它和第二个例子一样糟糕,但它看起来并没有错(这更糟糕)。所以,如果你不知道具体的细节,你会看到代码,会说没问题,如果出现性能问题,你可能会找到权宜之计。那么,我能把 GlobalConvert 放到我的库中吗?是的,我可以,但我不会这样做。

但是,如果您确实认为这是一个很好的补充,并且确定只会在非重复转换时使用它,那么您可以使用以下代码创建这样的类

using System;

namespace ConfigurableConvertTest
{
  public static class GlobalConvert
  {
    public static object ChangeType(object value, Type destinationType)
    {
      if (destinationType == null)
        throw new ArgumentNullException("destinationType");

      if (value == null)
        return null;

      var converter = Converters.LocalInstance.CastedGet(value.GetType(), destinationType);
      var result = converter(value);
      return result;
    }

    public static T ChangeType<T>(object value)
    {
      if (value == null)
        return default(T);

      object result = ChangeType(value, typeof(T));
      return (T)result;
    }
  }
}

我已经将其添加到下载代码中。但是,请记住,如果您使用它,请确保如果可以在进入循环之前获取转换器,则不要在循环中使用它。

Using the Code

要使用代码,您必须记住初始化 Converters 类是您的责任。此类可以处理所有可能的转换,但初始时没有任何转换。

但让我们从用法开始。要进行转换,您应该获取一个本地(线程特定)的转换器类实例,然后请求所有您想要的转换器。您将它们存储起来,然后才使用存储的转换器进行所有需要的转换。因此,一个好的模式是这样的

var localConverters = Converters.LocalInstance;
var intToString = localConverters.Get<int, string>();
var stringToInt = localConverters.Get<string, int>();
while(someCondition)
{
  int intVariable = stringToInt(someString);
  string stringVariable = intToString(someIntValue);
}

如果目标类型是在运行时而不是编译时获得的,您可以获取一个接收并返回值为对象的转换器。为此,请像这样调用 CastedGet 方法

var converter = localConverters.CastedGet(inputType, outputType);

当然,localConverters 必须已经存储(如上一个示例所示),并且 inputType 和 outputType 是源类型和目标类型。不同之处在于,只要您拥有与输入类型兼容的值,即使您将它们强制转换为对象,返回的转换器也将起作用。

但是要继续讨论如何使用代码,您至少需要注册一次转换器。那么,让我们看看

注册全局转换器

要添加全局转换(即对所有线程都有效的转换),您可以这样做

Converters.GlobalRegister<string, int>((value) => int.Parse(value));

在这种情况下,我正在注册一个字符串到整数的转换,执行转换的代码是一个匿名委托,它简单地执行一个 int.Parse。很简单,不是吗?

注册本地转换器

但是,如果您只想注册本地转换(即仅对实际线程有效),您应该首先获取 LocalInstance。我强烈建议您先获取 LocalInstance 并将其存储到变量中,这样如果您需要添加许多转换,您只需获取一次 LocalInstance。所以

var localConverters = Converters.LocalInstance;
localConverters.Register<string, int>((value) => int.Parse(value));
//localConverters.Register... some other conversion here.

通用转换

要注册一个已经存在的通用转换,只需将 SearchingConverterHandler 添加到 GlobalSearchingSearching 事件。Searching 事件还需要本地实例,因此,考虑到您已经存储了它,只需向本地线程添加处理程序即可添加此额外行

localConverters.Searching += StringToNullableConverter.SearchingConverterHandler;

StringToNullableConverter 能够处理从字符串到任何可空类型的转换,前提是存在从字符串到值类型的转换。也就是说,如果存在从字符串到 int 的转换,它就能够添加从字符串到可空 int 的转换。如果存在从字符串到 double 的转换,它将能够注册从字符串到可空 double 的转换。我想这足以理解它了。

创建您自己的通用转换逻辑

您知道已经存在一些通用转换器,并且您想创建自己的转换器,但目前您不知道如何操作。

我能做的最好的事情是尝试解释一个已经存在的通用转换器,所以,让我们看一个

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConfigurableConvertTest.GenericConverters
{
  public static class ToArrayConverter
  {
    public static Delegate Get(Type dataType)
    {
      if (dataType == null)
        throw new ArgumentNullException("dataType");

      var toArrayConverterType = typeof(ToArrayConverter<>).MakeGenericType(dataType);
      var instanceProperty = toArrayConverterType.GetProperty("Instance");
      var converter = instanceProperty.GetValue(null, null);
      return (Delegate)converter;
    }

    private static readonly SearchingConverterEventHandler _searchingConverterHandler = _SearchingConverter;
    public static SearchingConverterEventHandler SearchingConverterHandler
    {
      get
      {
        return _searchingConverterHandler;
      }
    }
    private static void _SearchingConverter(SearchingConverterEventArgs args)
    {
      var outputType = args.OutputType;
      if (!outputType.IsArray)
        return;

      var elementType = outputType.GetElementType();
      var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);

      var inputType = args.InputType;
      if (enumerableType.IsAssignableFrom(inputType))
        args.Converter = Get(elementType);
    }
  }

  public static class ToArrayConverter<T>
  {
    private static readonly Converter<IEnumerable<T>, T[]> _instance = _Convert;

    public static Converter<IEnumerable<T>, T[]> Instance
    {
      get
      {
        return _instance;
      }
    }

    private static T[] _Convert(IEnumerable<T> enumerable)
    {
      if (enumerable == null)
        return null;

      var result = enumerable.ToArray();
      return result;
    }
  }
}

这是 ToArrayConverter 的完整代码,没有文档注释。它分为两个类。泛型类(第二个)是真正进行转换的地方。如您所见,_Convert 方法简单地检查可枚举对象是否为 null,如果是则返回 null,否则调用该可枚举对象上的 ToArray() 方法。

这样的方法已经符合 Converter 委托,我将这样的委托存储在 _instance 变量中。但困难的部分是:如何从一个无类型事件处理程序中获取这样一个类型化的委托?以及如何检测到它是要获取的有效处理程序?

这就是非泛型类的原因。它使用反射通过数据类型(用于填充 T 的类型)获取泛型 ToArrayConverter,并获取实例属性的值。

用于填充 SearchingGlobalSearching 事件的代码检查预期的输出类型是否是数组,然后检查输入类型是否是具有与数组相同元素类型的可枚举类型。如果是,则它使用 Get 方法获取该转换器。我能说什么呢?泛型编程 + 反射真的没那么容易。

本地配置

Converters 类有一个全局配置和一个本地配置,但本地配置是基于线程的,所以您可能会认为它不起作用,因为

  • 您正在使用 ThreadPool 并且不知道该线程上可能已经配置了什么;
  • 您正在使用 Task,而不是 Thread
  • 您只想为当前方法更改某个转换,然后希望一切都像以前一样工作,而不会在当前线程的整个生命周期内保留更改;
  • 也许您还有其他想法。

我考虑过这些问题,并稍微改变了 Converters 搜索的实际工作方式。您还记得我说过 Converters 在本地搜索,如果找不到转换器,它会使用全局配置吗?

嗯,当它找不到转换器时,它会检查当前 Converters 实例的 Mode。该模式可以是

  • 基于前一个本地:在此模式下,本地配置会请求前一个本地配置执行搜索。因此,您可以想象,您可以创建和替换活动的 Converters 实例。
  • 基于全局:无论之前的本地配置(如果有)如何,它都会向全局配置请求转换器。
  • 隔离:顾名思义,它是隔离的。因此,如果本地未找到 Converter,即使存在有效的全局 Converter,也不会使用它。

设置模式

要设置模式,您应该创建一个新的 Converters 实例并为其指定一个模式。它将立即成为活动的 Converters 实例,并且在释放时,它将恢复上一个实例。我真的建议使用 using 语句创建新的 Converters 实例,像这样

using(var localConverters = new Converters(ConvertersMode.Isolated))
{
  // register the isolated conversions here...
  // call any methods that may use the converters class, in which case it will only 
  // see the converters registered in this block.
  // surely you can use a different Mode, but for this example, it is Isolated.
}
// here the old configuration takes place again.

通过这种简单的方法,您可以使配置在“作用域”内有效。当然,代码仍然绑定到线程,但这只是一个技术细节。代码不会关心它是否在 ThreadPool 线程中运行,是否在 Task 中运行,或者您的方法是如何被调用的。您可以告诉何时新的 Converters 生效,何时失效。因此,您可以创建真正本地化的配置,而无需将该配置传递给内部方法。

委托类型 - 两难境地

您可能已经注意到我使用了 Converter<TInput, TOutput> 委托。在实际开始编写 Converters 类之前,我曾考虑过使用 Func<T, TResult> 委托。但是,在查看是否已经存在泛型转换器时,我发现了 Converter<TInput, TOutput> 委托。

这让我认为存在某种泛型转换器,但在查看文档时,我发现它被列表和数组用于转换它们的所有项目,而在此上下文之外没有任何用途。

Func<T, TResult> 更为人所知,但一个名为 Converter 的委托看起来比一个名为 Func 的委托更适合进行转换。此外,TInputTOutput 看起来比 TTResult 更清晰。因此,我决定使用 Converter 委托。

这是个好选择吗?

我仍然不确定。我有一些旧代码,它们执行转换并使用 Func 委托。但是,由于我知道这些类在哪里以及它们在哪里使用,我简单地将它们更改为也使用 Converter 委托。但是,如果不是这种情况,我可以使用 Func 来填充 Converter,反之亦然吗?

答案是否定的。即使在实例化新的 FuncConverter 时可以使用相同的方法,我们也不能简单地将一个用于另一个。一种可能的解决方案是这样的

public Func<TInput, TOutput> ChangeConvertToFunc<TInput, TOutput>(Convert<TInput, TOutput> convert)
{
  return (input) => convert(input);
}

实际上,我们将创建一个调用前一个委托的新委托。这很好,对吗?

嗯,不。它确实有效,对于小东西来说可能没问题。但它增加了一个新的虚调用。如果我们将 Func 转换为 Convert,再将 Convert 转换为 Func 100 次,会发生什么?我们将最终得到 200 层间接调用。

那么,有没有更好的解决方案呢?

是的。我不知道为什么语言没有自动完成这一点,但是我们可以使用 Delegate.CreateDelegate 方法,通过使用源委托的 MethodTarget 来创建委托,因为该方法已经与输出委托兼容。

我们可以这样实现这种转换

public static TResultDelegate ConvertDelegate(Delegate input)
{
  if (input == null)
    return null;

  var target = input.Target;
  var method = input.Method;
  object result = Delegate.CreateDelegate(typeof(TResultDelegate), target, method);
  return (TResultDelegate)result;
}

此方法能够将任何输入委托转换为兼容的输出委托。因此,它能够将 Convert 委托转换为 Func 委托,反之亦然。

但是,为了使我的 Converters 类完整,我创建了 DelegateConverter 类,它有一个处理程序来填充从一种委托类型到另一种委托类型的转换。

也就是说,您可以使用 Converters 类将 int 转换为 string,将 Color 转换为 Brush,将 enum 转换为其基础类型,甚至将一种委托类型转换为另一种委托类型。

这仍然没有解决我是否应该使用 Convert 或 Func 委托的两难境地,但至少如果我最终遇到需要从一种类型更改为另一种类型的情况,我可以使用相同的 Converters 类来完成这项工作。

ConvertExtension 和 SmartBinding

在 WPF 示例中,您会注意到 XAML 中使用了 {App:Convert} 和 {App:SmartBinding}。

这两个类最终将绑定的 Converter 设置为 ConvertersConvert 实例。

为什么是两个类?我个人更喜欢 SmartBinding,但是,谁知道呢,也许您已经有了一个 Binding 子类,并且您只想使用通用转换器,所以,有一个替代方案。

我可以将 ConvertersConvert 作为资源并将其用于所有绑定吗?

不能。ConvertersConvert 在第一次转换时存储用于执行转换的委托,然后它一直重复使用相同的委托,以获得良好的性能。因此,如果所有 Binding 都具有相同的输入和输出类型,它将起作用。否则,它将抛出异常。如果您不确定,请不要尝试重复使用。

示例

有两个示例。一个是控制台应用程序,如果您调试它,它主要有用。它真正显示的唯一内容是枚举到整数数百万次转换的速度比较,但它也为了测试目的进行了许多其他转换。

还有一个 WPF 应用程序,它使用 ConverterExtensionSmartBinding 类。

如果您认为我将控制台应用程序用作 WPF 应用程序的库很奇怪,嗯,我也这么认为。但我确实认为用户会将其所需的类复制到自己的应用程序中,而不是将控制台应用程序作为库来使用。

这次我做的事情与我平时所做的相反,我的个人库没有提供下载。但是猜猜看,这个通用转换类将成为我个人库的一部分。

历史

  • 2012年9月20日:添加了对委托类型转换的支持并解释了 SmartBinding 类;
  • 2012年9月19日:添加了 ConvertersMode 和本地配置主题;
  • 2012年9月18日:添加了 StringToNullableConverter 并添加了使用代码主题;
  • 2012年9月17日:第一版。
© . All rights reserved.