diff --git a/src/System.ComponentModel.TypeConverter/ref/System.ComponentModel.TypeConverter.cs b/src/System.ComponentModel.TypeConverter/ref/System.ComponentModel.TypeConverter.cs
index 63aadacc5443..473cd7de23a5 100644
--- a/src/System.ComponentModel.TypeConverter/ref/System.ComponentModel.TypeConverter.cs
+++ b/src/System.ComponentModel.TypeConverter/ref/System.ComponentModel.TypeConverter.cs
@@ -499,6 +499,15 @@ public TimeSpanConverter() { }
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { throw null; }
public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType) { throw null; }
}
+ public partial class VersionConverter : System.ComponentModel.TypeConverter
+ {
+ public VersionConverter() { }
+ public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType) { throw null; }
+ public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Type destinationType) { throw null; }
+ public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { throw null; }
+ public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType) { throw null; }
+ public override bool IsValid(System.ComponentModel.ITypeDescriptorContext context, object value) { throw null; }
+ }
public partial class TypeConverter
{
public TypeConverter() { }
diff --git a/src/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj b/src/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj
index ec975148521b..9d39c46a76dc 100644
--- a/src/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj
+++ b/src/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs
index 0a74b5c05d0b..3cf183b87e26 100644
--- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs
+++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs
@@ -126,6 +126,7 @@ internal ReflectTypeDescriptionProvider()
[typeof(TimeSpan)] = typeof(TimeSpanConverter),
[typeof(Guid)] = typeof(GuidConverter),
[typeof(Uri)] = typeof(UriTypeConverter),
+ [typeof(Version)] = typeof(VersionConverter),
[typeof(Color)] = typeof(ColorConverter),
[typeof(Point)] = typeof(PointConverter),
[typeof(Rectangle)] = typeof(RectangleConverter),
diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/VersionConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/VersionConverter.cs
new file mode 100644
index 000000000000..23852ab6fa42
--- /dev/null
+++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/VersionConverter.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+
+namespace System.ComponentModel
+{
+ ///
+ /// Provides a type converter to convert Version objects to and
+ /// from various other representations.
+ ///
+ public class VersionConverter : TypeConverter
+ {
+ ///
+ /// Gets a value indicating whether this converter can convert an object in the
+ /// given source type to a Version.
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == null)
+ throw new ArgumentNullException(nameof(sourceType));
+
+ return sourceType == typeof(string) || sourceType == typeof(Version);
+ }
+
+ ///
+ /// Gets a value indicating whether this converter can
+ /// convert an object to the given destination type using the context.
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return destinationType == typeof(string) || destinationType == typeof(Version);
+ }
+
+ ///
+ /// Converts the given object to a Version.
+ ///
+ /// is not a valid version string
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value is string versionString)
+ {
+ try
+ {
+ return Version.Parse(versionString);
+ }
+ catch (Exception e)
+ {
+ throw new FormatException(SR.Format(SR.ConvertInvalidPrimitive, versionString, nameof(Version)), e);
+ }
+ }
+
+ if (value is Version version)
+ {
+ return new Version(version.Major, version.Minor, version.Build, version.Revision); // return new instance
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ /// Converts the given value object to
+ /// the specified destination type using the specified context and arguments.
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == null)
+ {
+ throw new ArgumentNullException(nameof(destinationType));
+ }
+
+ if (value is Version version)
+ {
+ if (destinationType == typeof(string))
+ return version.ToString();
+
+ if (destinationType == typeof(Version))
+ return new Version(version.Major, version.Minor, version.Build, version.Revision);
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ if (value is string version)
+ {
+ return Version.TryParse(version, out Version _);
+ }
+ return value is Version;
+ }
+ }
+}
diff --git a/src/System.ComponentModel.TypeConverter/tests/Performance/Perf.TypeDescriptorTests.cs b/src/System.ComponentModel.TypeConverter/tests/Performance/Perf.TypeDescriptorTests.cs
index 45353a8dde5b..af1187c2a4ca 100644
--- a/src/System.ComponentModel.TypeConverter/tests/Performance/Perf.TypeDescriptorTests.cs
+++ b/src/System.ComponentModel.TypeConverter/tests/Performance/Perf.TypeDescriptorTests.cs
@@ -45,6 +45,7 @@ public class Perf_TypeDescriptorTests
[InlineData(typeof(ClassIBase), typeof(IBaseConverter))]
[InlineData(typeof(ClassIDerived), typeof(IBaseConverter))]
[InlineData(typeof(Uri), typeof(UriTypeConverter))]
+ [InlineData(typeof(Version), typeof(VersionConverter))]
public static void GetConverter(Type typeToConvert, Type expectedConverter)
{
const int innerIterations = 100;
diff --git a/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj b/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj
index 70be8e528f6a..2d00902e8471 100644
--- a/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj
+++ b/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj
@@ -121,11 +121,13 @@
+
+
diff --git a/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs b/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs
index 86e5bcd6a29d..e31ae67ac9dc 100644
--- a/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs
+++ b/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs
@@ -8,7 +8,7 @@
namespace System.ComponentModel.Tests
{
- public class TypeDescriptorTests
+ public partial class TypeDescriptorTests
{
[Fact]
public void AddAndRemoveProvider()
@@ -66,16 +66,48 @@ public void GetAssociationReturnsExpectedObject()
Assert.Equal(secondaryObject, associatedObject);
}
- [Fact]
- public static void GetConverter()
+ [Theory]
+ [InlineData(typeof(bool), typeof(BooleanConverter))]
+ [InlineData(typeof(byte), typeof(ByteConverter))]
+ [InlineData(typeof(sbyte), typeof(SByteConverter))]
+ [InlineData(typeof(char), typeof(CharConverter))]
+ [InlineData(typeof(double), typeof(DoubleConverter))]
+ [InlineData(typeof(string), typeof(StringConverter))]
+ [InlineData(typeof(short), typeof(Int16Converter))]
+ [InlineData(typeof(int), typeof(Int32Converter))]
+ [InlineData(typeof(long), typeof(Int64Converter))]
+ [InlineData(typeof(float), typeof(SingleConverter))]
+ [InlineData(typeof(ushort), typeof(UInt16Converter))]
+ [InlineData(typeof(uint), typeof(UInt32Converter))]
+ [InlineData(typeof(ulong), typeof(UInt64Converter))]
+ [InlineData(typeof(object), typeof(TypeConverter))]
+ [InlineData(typeof(void), typeof(TypeConverter))]
+ [InlineData(typeof(DateTime), typeof(DateTimeConverter))]
+ [InlineData(typeof(DateTimeOffset), typeof(DateTimeOffsetConverter))]
+ [InlineData(typeof(decimal), typeof(DecimalConverter))]
+ [InlineData(typeof(TimeSpan), typeof(TimeSpanConverter))]
+ [InlineData(typeof(Guid), typeof(GuidConverter))]
+ [InlineData(typeof(Array), typeof(ArrayConverter))]
+ [InlineData(typeof(ICollection), typeof(CollectionConverter))]
+ [InlineData(typeof(Enum), typeof(EnumConverter))]
+ [InlineData(typeof(SomeEnum), typeof(EnumConverter))]
+ [InlineData(typeof(SomeValueType?), typeof(NullableConverter))]
+ [InlineData(typeof(int?), typeof(NullableConverter))]
+ [InlineData(typeof(ClassWithNoConverter), typeof(TypeConverter))]
+ [InlineData(typeof(BaseClass), typeof(BaseClassConverter))]
+ [InlineData(typeof(DerivedClass), typeof(DerivedClassConverter))]
+ [InlineData(typeof(IBase), typeof(IBaseConverter))]
+ [InlineData(typeof(IDerived), typeof(IBaseConverter))]
+ [InlineData(typeof(ClassIBase), typeof(IBaseConverter))]
+ [InlineData(typeof(ClassIDerived), typeof(IBaseConverter))]
+ [InlineData(typeof(Uri), typeof(UriTypeConverter))]
+ [InlineData(typeof(CultureInfo), typeof(CultureInfoConverter))]
+ public static void GetConverter(Type targetType, Type resultConverterType)
{
- foreach (Tuple pair in s_typesWithConverters)
- {
- TypeConverter converter = TypeDescriptor.GetConverter(pair.Item1);
- Assert.NotNull(converter);
- Assert.Equal(pair.Item2, converter.GetType());
- Assert.True(converter.CanConvertTo(typeof(string)));
- }
+ TypeConverter converter = TypeDescriptor.GetConverter(targetType);
+ Assert.NotNull(converter);
+ Assert.Equal(resultConverterType, converter.GetType());
+ Assert.True(converter.CanConvertTo(typeof(string)));
}
[Fact]
@@ -239,44 +271,5 @@ class FooBarDerived : FooBarBase
[Description("Derived")]
public override int Value { get; set; }
}
-
- private static Tuple[] s_typesWithConverters =
- {
- new Tuple (typeof(bool), typeof(BooleanConverter)),
- new Tuple (typeof(byte), typeof(ByteConverter)),
- new Tuple (typeof(sbyte), typeof(SByteConverter)),
- new Tuple (typeof(char), typeof(CharConverter)),
- new Tuple (typeof(double), typeof(DoubleConverter)),
- new Tuple (typeof(string), typeof(StringConverter)),
- new Tuple (typeof(short), typeof(Int16Converter)),
- new Tuple (typeof(int), typeof(Int32Converter)),
- new Tuple (typeof(long), typeof(Int64Converter)),
- new Tuple (typeof(float), typeof(SingleConverter)),
- new Tuple (typeof(ushort), typeof(UInt16Converter)),
- new Tuple (typeof(uint), typeof(UInt32Converter)),
- new Tuple (typeof(ulong), typeof(UInt64Converter)),
- new Tuple (typeof(object), typeof(TypeConverter)),
- new Tuple (typeof(void), typeof(TypeConverter)),
- new Tuple (typeof(DateTime), typeof(DateTimeConverter)),
- new Tuple (typeof(DateTimeOffset), typeof(DateTimeOffsetConverter)),
- new Tuple (typeof(decimal), typeof(DecimalConverter)),
- new Tuple (typeof(TimeSpan), typeof(TimeSpanConverter)),
- new Tuple (typeof(Guid), typeof(GuidConverter)),
- new Tuple (typeof(Array), typeof(ArrayConverter)),
- new Tuple (typeof(ICollection), typeof(CollectionConverter)),
- new Tuple (typeof(Enum), typeof(EnumConverter)),
- new Tuple (typeof(SomeEnum), typeof(EnumConverter)),
- new Tuple (typeof(SomeValueType?), typeof(NullableConverter)),
- new Tuple (typeof(int?), typeof(NullableConverter)),
- new Tuple (typeof(ClassWithNoConverter), typeof(TypeConverter)),
- new Tuple (typeof(BaseClass), typeof(BaseClassConverter)),
- new Tuple (typeof(DerivedClass), typeof(DerivedClassConverter)),
- new Tuple (typeof(IBase), typeof(IBaseConverter)),
- new Tuple (typeof(IDerived), typeof(IBaseConverter)),
- new Tuple (typeof(ClassIBase), typeof(IBaseConverter)),
- new Tuple (typeof(ClassIDerived), typeof(IBaseConverter)),
- new Tuple (typeof(Uri), typeof(UriTypeConverter)),
- new Tuple (typeof(CultureInfo), typeof(CultureInfoConverter))
- };
}
}
diff --git a/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.netcoreapp.cs b/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.netcoreapp.cs
new file mode 100644
index 000000000000..397c94d26f2e
--- /dev/null
+++ b/src/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.netcoreapp.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.ComponentModel.Tests
+{
+ public partial class TypeDescriptorTests
+ {
+ [Theory]
+ [InlineData(typeof(Version), typeof(VersionConverter))]
+ public static void GetConverter_NetCoreApp(Type targetType, Type resultConverterType) =>
+ GetConverter(targetType, resultConverterType);
+ }
+}
diff --git a/src/System.ComponentModel.TypeConverter/tests/VersionConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/VersionConverterTests.cs
new file mode 100644
index 000000000000..9e8ec3c905f6
--- /dev/null
+++ b/src/System.ComponentModel.TypeConverter/tests/VersionConverterTests.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.ComponentModel.Tests
+{
+ public class VersionConverterTests : ConverterTestBase
+ {
+ private static VersionConverter s_converter = new VersionConverter();
+
+ [Fact]
+ public static void CanConvertFrom_WithContext()
+ {
+ CanConvertFrom_WithContext(new object[2, 2]
+ {
+ { typeof(string), true },
+ { typeof(Version), true }
+ },
+ VersionConverterTests.s_converter);
+ }
+
+ [Fact]
+ public static void ConvertFrom_WithContext()
+ {
+ ConvertFrom_WithContext(new object[4, 3]
+ {
+ {"1.2", new Version(1, 2), null},
+ {"1.2.3", new Version(1, 2, 3), null},
+ {"1.2.3.4", new Version(1, 2, 3, 4), null},
+ {" 1.2.3.4 ", new Version(1, 2, 3, 4), null}
+ },
+ VersionConverterTests.s_converter);
+ }
+
+ [Fact]
+ public static void ConvertFromNull_WithContext_ThrowsNotSupportedException()
+ {
+ Assert.Throws(
+ () => VersionConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, null));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("1")]
+ [InlineData("1.-2")]
+ [InlineData("1.9999999999")]
+ public static void ConvertFromInvalidVersion_WithContext_ThrowsFormatException(string version)
+ {
+ Assert.Throws(
+ () => VersionConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, version));
+ }
+ }
+}