为什么 C# 标准库 (mscorlib) 不为其 类 提供抽象?
Why doesn't the C# Standard Library (mscorlib) provide abstractions for its classes?
C# 标准库目前没有为其大多数产品提供 abstractions/interfaces,例如 File
、HttpClient
等。(实际上,我不确定这个VB.NET、F#等都是这种情况,所以我们暂时坚持使用C#。)然而,需求相当高,因为单元测试实际上需要你模拟出对IO、网络等的依赖。有有很多自制解决方案,其中大部分 "wrappers."
我很好奇的是,为什么标准库没有通过简单地在库中包含接口来简化此操作。有设计原因吗?文体?
很简单,Microsoft 在最初构建 .NET 框架时无法预料到依赖注入和单元测试的广泛使用。
但这是一个常见问题,不仅适用于本机 .NET 类型,也适用于第 3 方库中的类型。处理没有抽象的类型的常用技术是使用 adapter pattern 进行抽象。然后你只需要使用你的 built-in 类型而不是库类型。
示例:UrlHelper
在 MVC 4 和之前 UrlHelper
class 没有虚拟成员,所以它不能被模拟或注入。要解决这个问题,只需要创建一个具有相同成员的接口(这可以使用 Visual Studio 中的 "extract interface" 功能轻松完成)...
public interface IUrlHelper
{
string Action(string actionName);
string Action(string actionName, object routeValues);
string Action(string actionName, string controllerName);
string Action(string actionName, string controllerName, object routeValues);
string Action(string actionName, string controllerName, object routeValues, string protocol);
string Action(string actionName, string controllerName, RouteValueDictionary routeValues);
string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName);
string Action(string actionName, RouteValueDictionary routeValues);
string Content(string contentPath);
string Encode(string url);
RequestContext RequestContext { get; }
RouteCollection RouteCollection { get; }
string RouteUrl(object routeValues);
string RouteUrl(string routeName);
string RouteUrl(string routeName, object routeValues);
string RouteUrl(string routeName, object routeValues, string protocol);
string RouteUrl(string routeName, RouteValueDictionary routeValues);
string RouteUrl(string routeName, RouteValueDictionary routeValues, string protocol, string hostName);
string RouteUrl(RouteValueDictionary routeValues);
bool IsLocalUrl(string url);
string HttpRouteUrl(string routeName, object routeValues);
string HttpRouteUrl(string routeName, RouteValueDictionary routeValues);
}
...然后创建一个继承UrlHelper
并实现IUrlHelper
...
的适配器类型
public class UrlHelperAdapter : UrlHelper, IUrlHelper
{
private UrlHelperAdapter(RequestContext requestContext)
: base(requestContext)
{
}
public UrlHelperAdapter(RequestContext requestContext, RouteCollection routeCollection)
: base(requestContext, routeCollection)
{
}
}
完成后,您可以将 IUrlHelper
传递给任何方法或构造函数,您只需将 UrlHelperAdapter
作为具体类型传递(或者如果使用模拟框架,构建一个模拟IUrlHelper
).
此技术适用于大多数 non-static 没有抽象的类型。
正如您已经发现的那样,有很多库可以通过构建自己的适配器为您完成跑腿工作,如果可以为您节省一些步骤,使用它们并没有错。
As mentioned in the comments, IO.Stream
already is an abstraction so there is no need in that specific case.
C# 标准库目前没有为其大多数产品提供 abstractions/interfaces,例如 File
、HttpClient
等。(实际上,我不确定这个VB.NET、F#等都是这种情况,所以我们暂时坚持使用C#。)然而,需求相当高,因为单元测试实际上需要你模拟出对IO、网络等的依赖。有有很多自制解决方案,其中大部分 "wrappers."
我很好奇的是,为什么标准库没有通过简单地在库中包含接口来简化此操作。有设计原因吗?文体?
很简单,Microsoft 在最初构建 .NET 框架时无法预料到依赖注入和单元测试的广泛使用。
但这是一个常见问题,不仅适用于本机 .NET 类型,也适用于第 3 方库中的类型。处理没有抽象的类型的常用技术是使用 adapter pattern 进行抽象。然后你只需要使用你的 built-in 类型而不是库类型。
示例:UrlHelper
在 MVC 4 和之前 UrlHelper
class 没有虚拟成员,所以它不能被模拟或注入。要解决这个问题,只需要创建一个具有相同成员的接口(这可以使用 Visual Studio 中的 "extract interface" 功能轻松完成)...
public interface IUrlHelper
{
string Action(string actionName);
string Action(string actionName, object routeValues);
string Action(string actionName, string controllerName);
string Action(string actionName, string controllerName, object routeValues);
string Action(string actionName, string controllerName, object routeValues, string protocol);
string Action(string actionName, string controllerName, RouteValueDictionary routeValues);
string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName);
string Action(string actionName, RouteValueDictionary routeValues);
string Content(string contentPath);
string Encode(string url);
RequestContext RequestContext { get; }
RouteCollection RouteCollection { get; }
string RouteUrl(object routeValues);
string RouteUrl(string routeName);
string RouteUrl(string routeName, object routeValues);
string RouteUrl(string routeName, object routeValues, string protocol);
string RouteUrl(string routeName, RouteValueDictionary routeValues);
string RouteUrl(string routeName, RouteValueDictionary routeValues, string protocol, string hostName);
string RouteUrl(RouteValueDictionary routeValues);
bool IsLocalUrl(string url);
string HttpRouteUrl(string routeName, object routeValues);
string HttpRouteUrl(string routeName, RouteValueDictionary routeValues);
}
...然后创建一个继承UrlHelper
并实现IUrlHelper
...
public class UrlHelperAdapter : UrlHelper, IUrlHelper
{
private UrlHelperAdapter(RequestContext requestContext)
: base(requestContext)
{
}
public UrlHelperAdapter(RequestContext requestContext, RouteCollection routeCollection)
: base(requestContext, routeCollection)
{
}
}
完成后,您可以将 IUrlHelper
传递给任何方法或构造函数,您只需将 UrlHelperAdapter
作为具体类型传递(或者如果使用模拟框架,构建一个模拟IUrlHelper
).
此技术适用于大多数 non-static 没有抽象的类型。
正如您已经发现的那样,有很多库可以通过构建自己的适配器为您完成跑腿工作,如果可以为您节省一些步骤,使用它们并没有错。
As mentioned in the comments,
IO.Stream
already is an abstraction so there is no need in that specific case.