Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Implement System.ComponentModel.VersionConverter (#28516)
Browse files Browse the repository at this point in the history
* Implement System.ComponentModel.VersionConverter

VersionConverter is a new System.ComponentModel.TypeConverter subclass that handle conversions between string and System.Version.

* Fix tests build for VersionTypeConverter

* Use Version.Parse() instead of new Version() to save an allocation

* Add test data to ensure that the version string is trimmed

* Call base class implementation instead of explicitly throwing

* Always throw a FormatException when a version string is invalid
  • Loading branch information
0xced authored and safern committed Aug 17, 2018
1 parent f6bb008 commit a5c7e72
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Compile Include="System\ComponentModel\UInt32Converter.cs" />
<Compile Include="System\ComponentModel\UInt64Converter.cs" />
<Compile Include="System\ComponentModel\UriTypeConverter.cs" />
<Compile Include="System\ComponentModel\VersionConverter.cs" />
<Compile Include="System\Timers\ElapsedEventArgs.cs" />
<Compile Include="System\Timers\ElapsedEventHandler.cs" />
<Compile Include="System\Timers\Timer.cs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <para>Provides a type converter to convert Version objects to and
/// from various other representations.</para>
/// </summary>
public class VersionConverter : TypeConverter
{
/// <summary>
/// <para>Gets a value indicating whether this converter can convert an object in the
/// given source type to a Version.</para>
/// </summary>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == null)
throw new ArgumentNullException(nameof(sourceType));

return sourceType == typeof(string) || sourceType == typeof(Version);
}

/// <summary>
/// <para>Gets a value indicating whether this converter can
/// convert an object to the given destination type using the context.</para>
/// </summary>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || destinationType == typeof(Version);
}

/// <summary>
/// <para>Converts the given object to a Version.</para>
/// </summary>
/// <exception cref="FormatException"><paramref name="value"/> is not a valid version string</exception>
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);
}

/// <summary>
/// <para>Converts the given value object to
/// the specified destination type using the specified context and arguments.</para>
/// </summary>
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@
<Compile Include="TypeConverterTests.cs" />
<Compile Include="TypeDescriptionProviderAttributeTests.cs" />
<Compile Include="TypeDescriptorTests.cs" />
<Compile Include="TypeDescriptorTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="TypeListConverterTests.cs" />
<Compile Include="UInt16ConverterTests.cs" />
<Compile Include="UInt32ConverterTests.cs" />
<Compile Include="UInt64ConverterTests.cs" />
<Compile Include="UriTypeConverterTests.cs" />
<Compile Include="VersionConverterTests.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="Drawing\StringTypeConverterTestBase.cs" />
<Compile Include="TimerTests.cs" />
<Compile Include="ContainerTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace System.ComponentModel.Tests
{
public class TypeDescriptorTests
public partial class TypeDescriptorTests
{
[Fact]
public void AddAndRemoveProvider()
Expand Down Expand Up @@ -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<Type, Type> 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]
Expand Down Expand Up @@ -239,44 +271,5 @@ class FooBarDerived : FooBarBase
[Description("Derived")]
public override int Value { get; set; }
}

private static Tuple<Type, Type>[] s_typesWithConverters =
{
new Tuple<Type, Type> (typeof(bool), typeof(BooleanConverter)),
new Tuple<Type, Type> (typeof(byte), typeof(ByteConverter)),
new Tuple<Type, Type> (typeof(sbyte), typeof(SByteConverter)),
new Tuple<Type, Type> (typeof(char), typeof(CharConverter)),
new Tuple<Type, Type> (typeof(double), typeof(DoubleConverter)),
new Tuple<Type, Type> (typeof(string), typeof(StringConverter)),
new Tuple<Type, Type> (typeof(short), typeof(Int16Converter)),
new Tuple<Type, Type> (typeof(int), typeof(Int32Converter)),
new Tuple<Type, Type> (typeof(long), typeof(Int64Converter)),
new Tuple<Type, Type> (typeof(float), typeof(SingleConverter)),
new Tuple<Type, Type> (typeof(ushort), typeof(UInt16Converter)),
new Tuple<Type, Type> (typeof(uint), typeof(UInt32Converter)),
new Tuple<Type, Type> (typeof(ulong), typeof(UInt64Converter)),
new Tuple<Type, Type> (typeof(object), typeof(TypeConverter)),
new Tuple<Type, Type> (typeof(void), typeof(TypeConverter)),
new Tuple<Type, Type> (typeof(DateTime), typeof(DateTimeConverter)),
new Tuple<Type, Type> (typeof(DateTimeOffset), typeof(DateTimeOffsetConverter)),
new Tuple<Type, Type> (typeof(decimal), typeof(DecimalConverter)),
new Tuple<Type, Type> (typeof(TimeSpan), typeof(TimeSpanConverter)),
new Tuple<Type, Type> (typeof(Guid), typeof(GuidConverter)),
new Tuple<Type, Type> (typeof(Array), typeof(ArrayConverter)),
new Tuple<Type, Type> (typeof(ICollection), typeof(CollectionConverter)),
new Tuple<Type, Type> (typeof(Enum), typeof(EnumConverter)),
new Tuple<Type, Type> (typeof(SomeEnum), typeof(EnumConverter)),
new Tuple<Type, Type> (typeof(SomeValueType?), typeof(NullableConverter)),
new Tuple<Type, Type> (typeof(int?), typeof(NullableConverter)),
new Tuple<Type, Type> (typeof(ClassWithNoConverter), typeof(TypeConverter)),
new Tuple<Type, Type> (typeof(BaseClass), typeof(BaseClassConverter)),
new Tuple<Type, Type> (typeof(DerivedClass), typeof(DerivedClassConverter)),
new Tuple<Type, Type> (typeof(IBase), typeof(IBaseConverter)),
new Tuple<Type, Type> (typeof(IDerived), typeof(IBaseConverter)),
new Tuple<Type, Type> (typeof(ClassIBase), typeof(IBaseConverter)),
new Tuple<Type, Type> (typeof(ClassIDerived), typeof(IBaseConverter)),
new Tuple<Type, Type> (typeof(Uri), typeof(UriTypeConverter)),
new Tuple<Type, Type> (typeof(CultureInfo), typeof(CultureInfoConverter))
};
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<NotSupportedException>(
() => 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<FormatException>(
() => VersionConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, version));
}
}
}

0 comments on commit a5c7e72

Please sign in to comment.