如何在 C# 中编写通用扩展方法来转换类型
How to Write Generic Extension Method to Convert Type in C#
我正在编写一个静态防护 class/api 来验证发送到方法的参数。
目前的代码如下:
public static class Guard
{
public static GuardArgument<T> Ensure<T>(T value, string argumentName)
{
return new GuardArgument<T>(value, argumentName);
}
public static T Value<T>(this GuardArgument<T> guardArgument)
{
return guardArgument.Value;
}
// Example extension method
public static GuardArgument<T> IsNotNull<T>(this GuardArgument<T> guardArgument, string errorMessage)
{
if (guardArgument.Value == null)
{
throw new ArgumentNullException(guardArgument.Name, errorMessage);
}
return guardArgument;
}
}
可以这么用:
public void Test(IFoo foo) {
Guard.Ensure(foo, "foo").IsNotNull();
}
现在的情况要求我需要从提供的接口转换为具体类型。不要问为什么,我就是需要!
我想向 GuardArgument
添加一个 As
扩展方法来执行此操作,例如:
public static GuardArgument<TOut> As<TOut, TIn>(this GuardArgument<TIn> guardArgument, Type type)
where TOut : class
{
// Check cast is OK, otherwise throw exception
return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
}
虽然我不太喜欢语法。我希望能够按如下方式使用 class:
Foo foo = Guard.Ensure(foo, "foo")
.As(typeof(Foo))
.IsNotNull()
.Value();
虽然我不确定如何编写扩展方法来允许这种语法。我意识到我可以使用现有的流利 API 作为:
Foo foo = Guard.Ensure(foo as Foo, "foo")
.IsNotNull()
.Value();
但从可读性的角度来看我不喜欢这个。
引入 GuardArgument{T} 实现的 IGuardArgument 接口。然后您可以从 As 扩展方法中删除 TIn 并删除 Type 参数。
签名:
public static GuardArgument<TOut> As(this IGuardArgument guardArgument);
用法:
Guard.Ensure(foo, "foo").As<Foo>().IsNotNull()
你可以得到这个语法:
Foo foo = Guard.Ensure(foo, "foo")
.As<Foo>()
.IsNotNull()
.Value();
诀窍是放弃 TIn
类型参数。它没有在 As()
方法中使用,并且在由于 TOut
而无法使用类型推断时使 API 膨胀。为了能够做到这一点而无需在所有类型上获得 As()
建议,您必须为您的 GuardArgument<>
class:
实现一个新的 non-generic 接口
interface IGuardArgument
{
object Value { get; }
strign Name { get; }
}
public class GuardArgument<T> : IGuardArgument
{
// Explicit implementation to hide this property from
// intellisense.
object IGuardArgument.Value { get { return Value; }
// Rest of class here, including public properties Value and Name.
}
现在你可以只用一个泛型参数编写 As()
方法:
public static GuardArgument<TOut> As<TOut>(this IGuardArgument guardArgument)
where TOut : class
{
// Check cast is OK, otherwise throw exception
return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
}
我正在编写一个静态防护 class/api 来验证发送到方法的参数。
目前的代码如下:
public static class Guard
{
public static GuardArgument<T> Ensure<T>(T value, string argumentName)
{
return new GuardArgument<T>(value, argumentName);
}
public static T Value<T>(this GuardArgument<T> guardArgument)
{
return guardArgument.Value;
}
// Example extension method
public static GuardArgument<T> IsNotNull<T>(this GuardArgument<T> guardArgument, string errorMessage)
{
if (guardArgument.Value == null)
{
throw new ArgumentNullException(guardArgument.Name, errorMessage);
}
return guardArgument;
}
}
可以这么用:
public void Test(IFoo foo) {
Guard.Ensure(foo, "foo").IsNotNull();
}
现在的情况要求我需要从提供的接口转换为具体类型。不要问为什么,我就是需要!
我想向 GuardArgument
添加一个 As
扩展方法来执行此操作,例如:
public static GuardArgument<TOut> As<TOut, TIn>(this GuardArgument<TIn> guardArgument, Type type)
where TOut : class
{
// Check cast is OK, otherwise throw exception
return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
}
虽然我不太喜欢语法。我希望能够按如下方式使用 class:
Foo foo = Guard.Ensure(foo, "foo")
.As(typeof(Foo))
.IsNotNull()
.Value();
虽然我不确定如何编写扩展方法来允许这种语法。我意识到我可以使用现有的流利 API 作为:
Foo foo = Guard.Ensure(foo as Foo, "foo")
.IsNotNull()
.Value();
但从可读性的角度来看我不喜欢这个。
引入 GuardArgument{T} 实现的 IGuardArgument 接口。然后您可以从 As 扩展方法中删除 TIn 并删除 Type 参数。 签名:
public static GuardArgument<TOut> As(this IGuardArgument guardArgument);
用法:
Guard.Ensure(foo, "foo").As<Foo>().IsNotNull()
你可以得到这个语法:
Foo foo = Guard.Ensure(foo, "foo")
.As<Foo>()
.IsNotNull()
.Value();
诀窍是放弃 TIn
类型参数。它没有在 As()
方法中使用,并且在由于 TOut
而无法使用类型推断时使 API 膨胀。为了能够做到这一点而无需在所有类型上获得 As()
建议,您必须为您的 GuardArgument<>
class:
interface IGuardArgument
{
object Value { get; }
strign Name { get; }
}
public class GuardArgument<T> : IGuardArgument
{
// Explicit implementation to hide this property from
// intellisense.
object IGuardArgument.Value { get { return Value; }
// Rest of class here, including public properties Value and Name.
}
现在你可以只用一个泛型参数编写 As()
方法:
public static GuardArgument<TOut> As<TOut>(this IGuardArgument guardArgument)
where TOut : class
{
// Check cast is OK, otherwise throw exception
return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
}