-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add TypeConverter fallback to DefaultValueAttribute #19354
Changes from 8 commits
aca9eb3
921b2f5
e2bce7b
c996bfe
304df3e
3909a3c
5075ea8
5d63db2
aee210a
8cae155
3390620
ea79dac
65cb4aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,11 +2,9 @@ | |
// 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; | ||
using System.ComponentModel; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.Runtime.InteropServices; | ||
using System.Reflection; | ||
using System.Threading; | ||
|
||
namespace System.ComponentModel | ||
{ | ||
|
@@ -23,6 +21,10 @@ public class DefaultValueAttribute : Attribute | |
/// </devdoc> | ||
private object _value; | ||
|
||
// We cache reflection types for TypeConverter conversion | ||
static Func<MethodInfo> s_getConverterMethodCached; | ||
static Func<MethodInfo> s_convertFromInvariantStringMethodCached; | ||
|
||
/// <devdoc> | ||
/// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class, converting the | ||
/// specified value to the | ||
|
@@ -36,7 +38,11 @@ public DefaultValueAttribute(Type type, string value) | |
// load an otherwise normal class. | ||
try | ||
{ | ||
if (type.IsSubclassOf(typeof(Enum))) | ||
if (TryConvertFromInvariantString(type, value, out object convertedValue)) | ||
{ | ||
_value = convertedValue; | ||
} | ||
else if (type.IsSubclassOf(typeof(Enum))) | ||
{ | ||
_value = Enum.Parse(type, value, true); | ||
} | ||
|
@@ -48,6 +54,40 @@ public DefaultValueAttribute(Type type, string value) | |
{ | ||
_value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); | ||
} | ||
|
||
return; | ||
|
||
// Try to load and use TypeConverter/TypeDescriptor types | ||
bool TryConvertFromInvariantString(Type typeToConvert, string stringValue, out object conversionResult) | ||
{ | ||
conversionResult = null; | ||
|
||
// lazy init reflection objects | ||
if (s_getConverterMethodCached == null) | ||
{ | ||
Type typeDescriptorType = Type.GetType("System.ComponentModel.TypeDescriptor, System, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should load the type from System.ComponentModel.TypeConverter directly, not from System facade. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because we'll know if TypeConverter exists when we call conversion method and so check could pass but fail after? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. System.dll is .NET Framework compatibility shim. It should not be loaded or pulled unless really necessary System.ComponentModel.TypeConverter is the native .NET Core implementation assembly for this, so it is where we should be loading this from. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh i misread...understood i used System... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
Volatile.Write(ref s_getConverterMethodCached, () => typeDescriptorType?.GetMethod("GetConverter", new Type[] { typeof(Type) })); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just cache the I would just have a Most importantly, it makes it easier for static analysis tools to analyze the reflection usage. IL Linkers will typically remove methods that don't appear used, and reflection usage is notoriously hard to statically analyze. Using patterns that are as straightforward as possible makes it less likely these things will get removed. The existing code is passing the type reflected on through a lambda capture and that's one of the harder things to analyze. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i followed this pattern, but there makes more sense Principal could change. Ok thank's for advice! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think you should be caching MethodInfos here at all. It should be all just delegates (much faster in steady state than MethodInfo). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
could you elaborate? Do you mean use Delegate.Create? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, like in the Principal example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...
var converter = getConverter(typeToConvert);
Func<string, object> del = (Func<string, object>)Delegate.CreateDelegate(typeof(Func<string, object>), converter, "ConvertFromInvariantString");
... @jkotas to call instance method ConvertFromInvariantString with delegate i need to pass concrete converter. What do you think?It's better than invoke(i don't have numbers)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should be able to create open delegate that takes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jkotas @ericstj i worked on open delegate(first time...very fun, if i could not eat to live i'd work 24/24 on this codebase for free, i'm learning a lot, exciting...thanks). ...
Volatile.Write(ref s_convertFromInvariantStringMethod, typeConverterType == null ? new object() : Delegate.CreateDelegate(typeof(Func<,,>).MakeGenericType(typeConverterType, typeof(string), typeof(object)), null, typeConverterType.GetMethod("ConvertFromInvariantString", new Type[] { typeof(string) })));
...
if (!(s_getConverterMethod is Func<Type, object> getConverter) || !(s_convertFromInvariantStringMethod is Delegate convertFromInvariantString))
return false;
conversionResult = convertFromInvariantString.DynamicInvoke(getConverter(typeToConvert), stringValue);
return true; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. I think the root cause of them problem is that you cannot reference TypeConverter type in CoreLib. I guess the options are to either give up on performance and just use MethodInfo; or add a helper method to System.ComponentModel.TypeConverter that does the whole conversion for you. It is https://github.com/dotnet/corefx/blob/b9d82ae7fd6912b6c0c758e47d0824689adaeafc/src/System.Runtime.Extensions/src/System/AppDomain.cs#L412 has done too - the GetDefaultInstance method is not part of the public surface, it is specific method for use by the AppDomain reflection (https://github.com/dotnet/corefx/blob/master/src/System.Security.Claims/src/System/Security/Claims/GenericPrincipal.cs#L88). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'll try this way! |
||
} | ||
|
||
if (s_convertFromInvariantStringMethodCached == null) | ||
{ | ||
Type typeConverterType = Type.GetType("System.ComponentModel.TypeConverter, System, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false); | ||
Volatile.Write(ref s_convertFromInvariantStringMethodCached, () => typeConverterType?.GetMethod("ConvertFromInvariantString", new Type[] { typeof(string) })); | ||
} | ||
|
||
// use local var to avoid double delegate invocation | ||
MethodInfo localGetConverterMethod = s_getConverterMethodCached(); | ||
MethodInfo localConvertFromInvariantStringMethod = s_convertFromInvariantStringMethodCached(); | ||
|
||
// if we didn't found required types on initialization fallback on old code | ||
if (localGetConverterMethod == null || localConvertFromInvariantStringMethod == null) | ||
return false; | ||
|
||
// typeConverter cannot be null GetConverter return default converter | ||
conversionResult = localConvertFromInvariantStringMethod.Invoke(localGetConverterMethod.Invoke(null, new[] { typeToConvert }), new[] { stringValue }); | ||
|
||
return true; | ||
} | ||
} | ||
catch | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would omit the
Cached
suffix.