简体   繁体   English

如何使用反射来避免价值装箱?

[英]How to use Reflection in order to avoid value boxing?

I am exploring Blazor's QuickGrid source code and found one interesting spot here .我正在探索 Blazor 的 QuickGrid源代码并在这里发现了一个有趣的地方。

On the 45th line Steve Sanderson left a TODO with a potentially better alternative solution.在第 45 行,Steve Sanderson 留下了一个 TODO 和一个可能更好的替代解决方案。

I could not resist my curiosity and decided to give it a try and benchmark the solution afterwards.我无法抗拒自己的好奇心,决定尝试一下,然后再对解决方案进行基准测试。 But, unfortunately, my knowledge in Reflection is really poor.但是,不幸的是,我在反射方面的知识真的很差。

Could anybody help me to understand how the ideas described in the Steve's comment could be achieved?谁能帮助我理解史蒂夫评论中描述的想法是如何实现的?

Thanks谢谢

UPD-1: Code snippet of what I have atm UPD-1:我所拥有的 atm 的代码片段

    if( typeof(TProp).GetInterface(nameof(IFormattable)) != null ) {

    Type result = typeof(Func<,>).MakeGenericType(typeof(TGridItem), typeof(TProp));
        
    _cellTextFunc = item => FormatValue(compiledPropertyExpression);
}

    // TODO: Consider using reflection to avoid having to box every value just to call IFormattable.ToString
    // For example, define a method "string Format<U>(Func<TGridItem, U> property) where U: IFormattable", and
    // then construct the closed type here with U=TProp when we know TProp implements IFormattable

    private string FormatValue<U>( Func<TGridItem, U> property ) where U : IFormattable
    {
        return null;
    }

I suspect what Steve meant was to define a method such as:我怀疑史蒂夫的意思是定义一个方法,例如:

private string? FormatItem<U>(TGridItem item, Func<TGridItem, U> property) where U: IFormattable
{
    return property(item)?.ToString(Format, null);
}

(I had to add item to make it make sense: perhaps I'm missing something). (我不得不添加item以使其有意义:也许我遗漏了一些东西)。

We also need to define a spearate version for nullables, since Nullable<T> can't satisfy the constraint IFormattable<T> , even if T does implement IFormattable<T> .我们还需要为 nullable 定义一个单独的版本,因为Nullable<T>不能满足约束IFormattable<T> ,即使T确实实现IFormattable<T>

private string? FormatItemNullable<U>(TGridItem item, Func<TGridItem, U?> property) where U : struct, IFormattable
{
    return property(item)?.ToString(Format, null);
}

Then once you know U , you can create a delegate which invokes this method:然后,一旦您知道U ,就可以创建一个调用此方法的委托:

var formatItemMethodInfo = typeof(C<TGridItem, TProp>).GetMethod("FormatItem", BindingFlags.NonPublic | BindingFlags.Instance)!;
var formatItemNullableMethodInfo = typeof(C<TGridItem, TProp>).GetMethod("FormatItemNullable", BindingFlags.NonPublic | BindingFlags.Instance)!;

var formatter = (Func<TGridItem, Func<TGridItem, TProp>, string>)Delegate.CreateDelegate(
    typeof(Func<TGridItem, Func<TGridItem, TProp>, string>),
    this,
    nullableUnderlyingTypeOrNull == null
        ? formatItemMethodInfo.MakeGenericMethod(typeof(TProp))
        : formatItemNullableMethodInfo.MakeGenericMethod(nullableUnderlyingTypeOrNull));

And finally use this when constructing _cellTextFunc :最后在构造_cellTextFunc时使用它:

_cellTextFunc = item => formatter(item, compiledPropertyExpression);

This has a little bit of upfront cost (creating the delegate), but invoking it is cheap, and means that you don't have to box item to IFormattable if it's a value type.这有一点前期成本(创建委托),但调用它很便宜,并且意味着如果它是值类型,您不必将item装箱到IFormattable

See on SharpLab . 请参阅 SharpLab


If I were to suggest a better solution, since we're already using compiled expressions with Property , I'd extend that and compile the ToString call into the expression.如果我要提出更好的解决方案,因为我们已经在使用带有Property的编译表达式,我会扩展它并将ToString调用编译到表达式中。 Something like:就像是:

var toStringMethod = typeof(IFormattable).GetMethod("ToString")!;

var propVar = Expression.Variable(typeof(TProp), "prop");

// What to call String(...) on. We need to call prop.GetValueOrDefault().ToString(...)
// if prop is a nullable value type
Expression toStringCallTarget = nullableUnderlyingTypeOrNull == null
    ? propVar
    : Expression.Call(
        propVar,
        typeof(TProp).GetMethod("GetValueOrDefault", Type.EmptyTypes)!);

// The ToString(...) call itself
var toStringCall = Expression.Call(
    toStringCallTarget,
    toStringMethod,
    Expression.Property(Expression.Constant(this), "Format"),
    Expression.Constant(null, typeof(IFormatProvider)));

// If prop is nullable (class or nullable struct), then we need to do
// prop == null ? null : prop[.GetValueOrDefault()].ToString(...),
// otherwise just call prop.ToString(...)
Expression conditionalCall = default(TProp) == null
    ? Expression.Condition(
        Expression.Equal(propVar, Expression.Constant(null, typeof(TProp))),
        Expression.Constant(null, typeof(string)),
        toStringCall)
    : toStringCall;

var block = Expression.Block(new[] { propVar },
    Expression.Assign(propVar, Property.Body),
    conditionalCall);

_cellTextFunc = Expression.Lambda<Func<TGridItem, string?>>(block, Property.Parameters[0]).Compile();

See it on SharpLab . 在 SharpLab 上查看


Just for fun, I put together a benchmark of the original approaches, Steve's proposed approach, and mine and MarcGravell's proposed solutions:只是为了好玩,我将原始方法的基准、史蒂夫提出的方法以及我和 MarcGravell 提出的解决方案放在一起:

namespace MyBenchmarks
{
    [MemoryDiagnoser]
    public class Benchmark
    {
        private readonly GridItem gridItem = new();

        private readonly Func<GridItem, string?> originalClass = new Original<GridItem, FormattableClass>() { Property = x => x.Class, Format = "X" }.Test();
        private readonly Func<GridItem, string?> originalNonNullable = new Original<GridItem, FormattableStruct>() { Property = x => x.NonNullable, Format = "X" }.Test();
        private readonly Func<GridItem, string?> originalNullable = new Original<GridItem, FormattableStruct?>() { Property = x => x.Nullable, Format = "X" }.Test();

        private readonly Func<GridItem, string?> reflectionClass = new ReflectionTest<GridItem, FormattableClass>() { Property = x => x.Class, Format = "X" }.Test();
        private readonly Func<GridItem, string?> reflectionNonNullable = new ReflectionTest<GridItem, FormattableStruct>() { Property = x => x.NonNullable, Format = "X" }.Test();
        private readonly Func<GridItem, string?> reflectionNullable = new ReflectionTest<GridItem, FormattableStruct?>() { Property = x => x.Nullable, Format = "X" }.Test();

        private readonly Func<GridItem, string?> formatterClass = new FormatterTest<GridItem, FormattableClass>() { Property = x => x.Class, Format = "X" }.Test();
        private readonly Func<GridItem, string?> formatterNonNullable = new FormatterTest<GridItem, FormattableStruct>() { Property = x => x.NonNullable, Format = "X" }.Test();
        private readonly Func<GridItem, string?> formatterNullable = new FormatterTest<GridItem, FormattableStruct?>() { Property = x => x.Nullable, Format = "X" }.Test();

        private readonly Func<GridItem, string?> compiledExpressionClass = new CompiledExpressionTest<GridItem, FormattableClass>() { Property = x => x.Class, Format = "X" }.Test();
        private readonly Func<GridItem, string?> compiledExpressionNonNullable = new CompiledExpressionTest<GridItem, FormattableStruct>() { Property = x => x.NonNullable, Format = "X" }.Test();
        private readonly Func<GridItem, string?> compiledExpressionNullable = new CompiledExpressionTest<GridItem, FormattableStruct?>() { Property = x => x.Nullable, Format = "X" }.Test();

        [Benchmark]
        public void OriginalClass() => originalClass(gridItem);

        [Benchmark]
        public void OriginalNonNullable() => originalNonNullable(gridItem);

        [Benchmark]
        public void OriginalNullable() => originalNullable(gridItem);

        [Benchmark]
        public void ReflectionClass() => reflectionClass(gridItem);

        [Benchmark]
        public void ReflectionNonNullable() => reflectionNonNullable(gridItem);

        [Benchmark]
        public void ReflectionNullable() => reflectionNullable(gridItem);

        [Benchmark]
        public void FormatterClass() => formatterClass(gridItem);

        [Benchmark]
        public void FormatterNonNullable() => formatterNonNullable(gridItem);

        [Benchmark]
        public void FormatterNullable() => formatterNullable(gridItem);

        [Benchmark]
        public void CompiledExpressionClass() => compiledExpressionClass(gridItem);

        [Benchmark]
        public void CompiledExpressionNonNullable() => compiledExpressionNonNullable(gridItem);

        [Benchmark]
        public void CompiledExpressionNullable() => compiledExpressionNullable(gridItem);
    }

    class Original<TGridItem, TProp>
    {
        public Expression<Func<TGridItem, TProp>> Property { get; set; } = default!;
        public string? Format { get; set; }

        public Func<TGridItem, string?> Test()
        {
            Func<TGridItem, string?> cellTextFunc;

            var compiledPropertyExpression = Property.Compile();
            if (!string.IsNullOrEmpty(Format))
            {
                // TODO: Consider using reflection to avoid having to box every value just to call IFormattable.ToString
                // For example, define a method "string Format<U>(Func<TGridItem, U> property) where U: IFormattable", and
                // then construct the closed type here with U=TProp when we know TProp implements IFormattable

                // If the type is nullable, we're interested in formatting the underlying type
                var nullableUnderlyingTypeOrNull = Nullable.GetUnderlyingType(typeof(TProp));

                if (!typeof(IFormattable).IsAssignableFrom(nullableUnderlyingTypeOrNull ?? typeof(TProp)))
                {
                    throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'.");
                }

                cellTextFunc = item => ((IFormattable?)compiledPropertyExpression!(item))?.ToString(Format, null);
            }
            else
            {
                cellTextFunc = item => compiledPropertyExpression!(item)?.ToString();
            }

            return cellTextFunc;
        }

        private string? FormatItem<U>(TGridItem item, Func<TGridItem, U> property) where U : IFormattable
        {
            return property(item)?.ToString(Format, null);
        }
    }

    class ReflectionTest<TGridItem, TProp>
    {
        private static readonly MethodInfo formatItemMethodInfo = typeof(ReflectionTest<TGridItem, TProp>).GetMethod("FormatItem", BindingFlags.NonPublic | BindingFlags.Instance)!;
        private static readonly MethodInfo formatItemNullableMethodInfo = typeof(ReflectionTest<TGridItem, TProp>).GetMethod("FormatItemNullable", BindingFlags.NonPublic | BindingFlags.Instance)!;
        public Expression<Func<TGridItem, TProp>> Property { get; set; } = default!;
        public string? Format { get; set; }

        public Func<TGridItem, string?> Test()
        {
            Func<TGridItem, string?> cellTextFunc;

            var compiledPropertyExpression = Property.Compile();
            if (!string.IsNullOrEmpty(Format))
            {
                // If the type is nullable, we're interested in formatting the underlying type
                var nullableUnderlyingTypeOrNull = Nullable.GetUnderlyingType(typeof(TProp));
     
                if (!typeof(IFormattable).IsAssignableFrom(nullableUnderlyingTypeOrNull ?? typeof(TProp)))
                {
                    throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'.");
                }

                var formatter = (Func<TGridItem, Func<TGridItem, TProp>, string>)Delegate.CreateDelegate(
                    typeof(Func<TGridItem, Func<TGridItem, TProp>, string>),
                    this,
                    nullableUnderlyingTypeOrNull == null
                        ? formatItemMethodInfo.MakeGenericMethod(typeof(TProp))
                        : formatItemNullableMethodInfo.MakeGenericMethod(nullableUnderlyingTypeOrNull));
                cellTextFunc = item => formatter(item, compiledPropertyExpression);
            }
            else
            {
                cellTextFunc = item => compiledPropertyExpression!(item)?.ToString();
            }

            return cellTextFunc;
        }

        private string? FormatItem<U>(TGridItem item, Func<TGridItem, U> property) where U : IFormattable
        {
            return property(item)?.ToString(Format, null);
        }

        private string? FormatItemNullable<U>(TGridItem item, Func<TGridItem, U?> property) where U : struct, IFormattable
        {
            return property(item)?.ToString(Format, null);
        }
    }

    public interface IFormatter<T>
    {
        string Format(T value, string? format, IFormatProvider? formatProvider = null);
    }

    public static class Formatter<T>
    {
        public static IFormatter<T>? Instance { get; }

        static Formatter()
        {
            object? instance = null;
            var underlying = Nullable.GetUnderlyingType(typeof(T));
            if (typeof(IFormattable).IsAssignableFrom(underlying ?? typeof(T)))
            {
                if (typeof(T).IsValueType)
                {
                    if (underlying is null)
                    {
                        instance = Activator.CreateInstance(typeof(SupportedValueTypeFormatter<>).MakeGenericType(typeof(T)));
                    }
                    else
                    {
                        instance = Activator.CreateInstance(typeof(SupportedNullableFormatter<>).MakeGenericType(underlying));
                    }
                }
                else
                {
                    instance = Activator.CreateInstance(typeof(SupportedRefTypeFormatter<>).MakeGenericType(typeof(T)));
                }
            }
            Instance = (IFormatter<T>?)instance;
        }
    }

    internal sealed class SupportedNullableFormatter<T> : IFormatter<T?>
        where T : struct, IFormattable
    {
        public string Format(T? value, string? format, IFormatProvider? formatProvider)
            => value is null ? "" : value.GetValueOrDefault().ToString(format, formatProvider);
    }

    internal sealed class SupportedValueTypeFormatter<T> : IFormatter<T>
        where T : struct, IFormattable
    {
        public string Format(T value, string? format, IFormatProvider? formatProvider)
            => value.ToString(format, formatProvider);
    }
    internal sealed class SupportedRefTypeFormatter<T> : IFormatter<T>
        where T : class, IFormattable
    {
        public string Format(T value, string? format, IFormatProvider? formatProvider)
            => value is null ? "" : value.ToString(format, formatProvider);
    }

    class FormatterTest<TGridItem, TProp>
    {
        public Expression<Func<TGridItem, TProp>> Property { get; set; } = default!;
        public string? Format { get; set; }

        public Func<TGridItem, string?> Test()
        {
            Func<TGridItem, string?> cellTextFunc;

            var compiledPropertyExpression = Property.Compile();
            if (!string.IsNullOrEmpty(Format))
            {
                // If the type is nullable, we're interested in formatting the underlying type
                var nullableUnderlyingTypeOrNull = Nullable.GetUnderlyingType(typeof(TProp));

                if (!typeof(IFormattable).IsAssignableFrom(nullableUnderlyingTypeOrNull ?? typeof(TProp)))
                {
                    throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'.");
                }

                cellTextFunc = item => Formatter<TProp>.Instance!.Format(compiledPropertyExpression!(item), Format, null);
            }
            else
            {
                cellTextFunc = item => compiledPropertyExpression!(item)?.ToString();
            }

            return cellTextFunc;
        }
    }

    class CompiledExpressionTest<TGridItem, TProp>
    {
        private static readonly MethodInfo toStringMethod = typeof(IFormattable).GetMethod("ToString")!;
        public Expression<Func<TGridItem, TProp>> Property { get; set; } = default!;
        public string? Format { get; set; }

        public Func<TGridItem, string?> Test()
        {
            Func<TGridItem, string?> cellTextFunc;

            if (!string.IsNullOrEmpty(Format))
            {
                // If the type is nullable, we're interested in formatting the underlying type
                var nullableUnderlyingTypeOrNull = Nullable.GetUnderlyingType(typeof(TProp));

                if (!typeof(IFormattable).IsAssignableFrom(nullableUnderlyingTypeOrNull ?? typeof(TProp)))
                {
                    throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'.");
                }

                var propVar = Expression.Variable(typeof(TProp), "prop");

                Expression toStringCallTarget = nullableUnderlyingTypeOrNull == null
                    ? propVar
                    : Expression.Call(
                        propVar,
                        typeof(TProp).GetMethod("GetValueOrDefault", Type.EmptyTypes)!);

                var toStringCall = Expression.Call(
                    toStringCallTarget,
                    toStringMethod,
                    Expression.Property(Expression.Constant(this), "Format"),
                    Expression.Constant(null, typeof(IFormatProvider)));

                Expression conditionalCall = default(TProp) == null
                    ? Expression.Condition(
                        Expression.Equal(propVar, Expression.Constant(null, typeof(TProp))),
                        Expression.Constant(null, typeof(string)),
                        toStringCall)
                    : toStringCall;

                var block = Expression.Block(new[] { propVar },
                    Expression.Assign(propVar, Property.Body),
                    conditionalCall);

                cellTextFunc = Expression.Lambda<Func<TGridItem, string?>>(block, Property.Parameters[0]).Compile();
            }
            else
            {
                cellTextFunc = item => Property.Compile()!(item)?.ToString();
            }

            return cellTextFunc;
        }
    }

    public class GridItem
    {
        public FormattableClass Class { get; } = new FormattableClass();
        public FormattableStruct NonNullable => new FormattableStruct();
        public FormattableStruct? Nullable => new FormattableStruct();
    }

    public class FormattableClass : IFormattable
    {
        public string ToString(string? format, IFormatProvider? formatProvider) => "";
    }

    public struct FormattableStruct : IFormattable
    {
        public string ToString(string? format, IFormatProvider? formatProvider) => "";
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            BenchmarkRunner.Run<Benchmark>();
        }
    }
}

With the results:结果:

Method方法 Mean意思 Error错误 StdDev标准偏差 Gen0 Gen0 Allocated已分配
OriginalClass原创类 6.111 ns 6.111 纳秒 0.1206 ns 0.1206 纳秒 0.1128 ns 0.1128 纳秒 - - - -
OriginalNonNullable OriginalNonNullable 7.568 ns 7.568 纳秒 0.1793 ns 0.1793纳秒 0.1677 ns 0.1677 纳秒 0.0038 0.0038 24 B 24乙
OriginalNullable原始可空 54.260 ns 54.260 纳秒 1.0880 ns 1.0880 纳秒 1.4893 ns 1.4893 纳秒 0.0038 0.0038 24 B 24乙
ReflectionClass反射类 6.750 ns 6.750 纳秒 0.0630 ns 0.0630 纳秒 0.0590 ns 0.0590 纳秒 - -
ReflectionNonNullable反射不可为空 4.710 ns 4.710 纳秒 0.0514 ns 0.0514 纳秒 0.0456 ns 0.0456 纳秒 - -
ReflectionNullable反射可空 7.374 ns 7.374 纳秒 0.0819 ns 0.0819纳秒 0.0726 ns 0.0726 纳秒 - -
FormatterClass格式化程序类 14.054 ns 14.054 纳秒 0.2079 ns 0.2079 纳秒 0.1843 ns 0.1843 纳秒 - - - -
FormatterNonNullable FormatterNonNullable 3.521 ns 3.521 纳秒 0.0907 ns 0.0907纳秒 0.0849 ns 0.0849纳秒 - - - -
FormatterNullable FormatterNullable 7.156 ns 7.156 纳秒 0.0889 ns 0.0889纳秒 0.0832 ns 0.0832纳秒 - - - -
CompiledExpressionClass编译表达式类 2.919 ns 2.919 纳秒 0.0864 ns 0.0864 纳秒 0.0888 ns 0.0888纳秒 - - - -
CompiledExpressionNonNullable CompiledExpressionNonNullable 1.815 ns 1.815 纳秒 0.0405 ns 0.0405纳秒 0.0379 ns 0.0379纳秒 - - - -
CompiledExpressionNullable CompiledExpressionNullable 1.799 ns 1.799 纳秒 0.0577 ns 0.0577 纳秒 0.0686 ns 0.0686 纳秒 - - - -

As you can see, the only solution which boxes is the original code.如您所见,唯一的解决方案是原始代码。 The reflection-based approach is faster than Marc's solution for classes, and about the same for structs.基于反射的方法比 Marc 的类解决方案更快,对于结构来说也差不多。 However, the expression-based approach is significantly faster than anything.然而,基于表达式的方法比任何方法都快得多。

I've no idea where that extra cost in FormatterClass is coming from, but it's repeatable.我不知道FormatterClass中的额外成本从何而来,但它是可重复的。

I'm deliberately ignoring the cost of constructing the Func<TGridItem, string> in this benchmark: the original code is obviously optimised for the case where Property rarely changes, and the cost of compiling Property is going to dominate anything else most likely.我故意忽略了在此基准测试中构造Func<TGridItem, string>的成本:原始代码显然针对Property很少更改的情况进行了优化,并且编译Property的成本很可能会支配其他任何东西。

I would go with a generic interface such as IFormatter<T> without a generic constraint, and a few private implementations with the necessary complaint, and use reflection internally to decide which private implementation to use - a simple version for everything except Nullable<T> , and a special-case for that;我会 go 有一个通用接口,例如没有通用约束的IFormatter<T>和一些带有必要投诉的私有实现,并在内部使用反射来决定使用哪个私有实现——一个简单的版本,除了Nullable<T>之外的所有东西,以及一个特例; this is the same approach used by EqualityComparer<T>.Default .这与EqualityComparer<T>.Default使用的方法相同。

Full example is below, noting that Formatter<T>.Instance will be null if the T doesn't support IFormattable (to allow testing, although this could also be handled separately).完整示例如下,注意Formatter<T>.Instance将是null如果T不支持IFormattable (允许测试,尽管这也可以单独处理)。

The use of generics in the private implementation means we're using "constrained call", so: no boxing there.在私有实现中使用 generics 意味着我们正在使用“约束调用”,因此:那里没有装箱。 The special-casing of Nullable<T> means we can handle that too, with an additional indirection. Nullable<T>的特殊外壳意味着我们也可以通过额外的间接处理来处理它。

The important thing is that we only do the thinking once per type - testing whether the type is nullable, whether it implements IFormattable , etc - and this is then cached via a strategy instance.重要的是我们只对每个类型进行一次思考——测试该类型是否可为空,它是否实现IFormattable等——然后通过策略实例进行缓存。 If we do the thinking every time: boxing would probably be cheaper!如果我们每次都这么想:拳击可能会更便宜!

Relevant usage for the sample provided (note you could probably simply with a non-generic static class with generic method so you can just do Formatter.Format(value, format, provider) and use generic inference from value for the rest):所提供示例的相关用法(请注意,您可能只需使用非通用 static class 和通用方法,这样您就可以执行Formatter.Format(value, format, provider)并使用来自value的通用推理来完成其余部分):

// L50
if (Formatter<TProp>.Instance is null)
{
    throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'.");
}
_cellTextFunc = item => Formatter<TProp>.Instance.Format(compiledPropertyExpression!(item), Format, null);

Console.WriteLine(Formatter<int>.Instance!.Format(123, "G"));
Console.WriteLine(Formatter<int?>.Instance!.Format(123, "G"));
Console.WriteLine(Formatter<int?>.Instance!.Format(null, "G"));
Console.WriteLine(Formatter<NotFormattable?>.Instance is null);

struct NotFormattable { }

public interface IFormatter<T>
{
    string Format(T value, string? format, IFormatProvider? formatProvider = null);
}

public static class Formatter<T>
{
    public static IFormatter<T>? Instance { get; }

    static Formatter()
    {
        object? instance = null;
        var underlying = Nullable.GetUnderlyingType(typeof(T));
        if (typeof(IFormattable).IsAssignableFrom(underlying ?? typeof(T)))
        {
            if (typeof(T).IsValueType)
            {
                if (underlying is null)
                {
                    instance = Activator.CreateInstance(typeof(SupportedValueTypeFormatter<>).MakeGenericType(typeof(T)));
                }
                else
                {
                    instance = Activator.CreateInstance(typeof(SupportedNullableFormatter<>).MakeGenericType(underlying));
                }
            }
            else
            {
                instance = Activator.CreateInstance(typeof(SupportedRefTypeFormatter<>).MakeGenericType(typeof(T)));
            }
        }
        Instance = (IFormatter<T>?)instance;

    }
}
internal sealed class SupportedNullableFormatter<T> : IFormatter<T?>
    where T : struct, IFormattable
{
    public string Format(T? value, string? format, IFormatProvider? formatProvider)
        => value is null ? "" : value.GetValueOrDefault().ToString(format, formatProvider);
}

internal sealed class SupportedValueTypeFormatter<T> : IFormatter<T>
    where T : struct, IFormattable
{
    public string Format(T value, string? format, IFormatProvider? formatProvider)
        => value.ToString(format, formatProvider);
}
internal sealed class SupportedRefTypeFormatter<T> : IFormatter<T>
    where T : class, IFormattable
{
    public string Format(T value, string? format, IFormatProvider? formatProvider)
        => value is null ? "" : value.ToString(format, formatProvider);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM