带有事件处理程序的 HttpClient 扩展
HttpClient Extension with Eventhandler
我正在尝试使用 EventHandler 扩展 HttpClient。
这可能吗?
我在 HttpClient 上有如下扩展:
public static class HttpClientExtensions
{
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
using var response = await client.GetAsync(url);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEvent(null, new HttpClientErrorEventArgs()
{
StatusCode = response.StatusCode,
Message = $"{response.StatusCode } {(int)response.StatusCode } "
});
return default(T);
}
response.EnsureSuccessStatusCode();
[... ]
}
}
public class HttpClientErrorEventArgs : EventArgs
{
public System.Net.HttpStatusCode StatusCode { get; set; }
public string Message { get; set; }
}
但是如何定义 HttpClientErrorEvent?
我尝试了以下但它不是特定 HttpClient 的扩展:
public static event EventHandler<HttpClientErrorEventArgs> HttpClientErrorEvent = delegate { };
您可以将处理程序存储在您的扩展程序中 class 并执行类似的操作吗?请注意此代码不是线程安全的,需要围绕字典和列表访问进行同步!
public static class HttpClientExtensions
{
private static Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>> Handlers { get; set; }
static HttpClientExtensions()
{
Handlers = new Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>>();
}
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
////code ....
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEventArgs args = null;
client.RaiseEvent(args);
return default(T);
////code
}
public static void AddHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (!found)
{
handlers = new List<Action<HttpClientErrorEventArgs>>();
Handlers[client] = handlers;
}
handlers.Add(handler);
}
public static void RemoveHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
handlers.Remove(handler);
if (handlers.Count == 0)
{
Handlers.Remove(client);
}
}
}
private static void RaiseEvent(this HttpClient client, HttpClientErrorEventArgs args)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
foreach (var handler in handlers)
{
handler.Invoke(args);
}
}
}
}
不要使用事件来 return 错误。对于初学者,您将如何确定哪个请求引发了哪个错误?您必须围绕每个调用注册和取消注册事件处理程序,但您将如何处理并发调用?您将如何编写多个此类调用?
错误无论如何都不是事件。充其量,您必须像处理回调一样处理事件 - 在这种情况下,为什么不使用实际的回调?
public async static Task<T> GetSomethingSpecialAsync<T>(this HttpClient client, string url,Action<(HttpStatusCode Status,string Message)> onError)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
onError(response.Status,....);
return default;
}
}
...
var value=await client.GetSomethingSpesialAsync(url,
(status,msg)=>{Console.WriteLine($"Calling {url} Failed with {status}:{msg}");}
);
async/await
的创建是为了让人们可以摆脱回调和事件。用事件组合多个异步调用几乎是不可能的,并且很难用回调来做到这一点。这就是为什么 很多 语言(C#、JavaScript、Dart,甚至在某种程度上是 C++)引入了 promises 和 async/await
来摆脱成功和错误回调。
您实际上可以 return 调用函数的结果或错误,而不是调用回调。这是一种嵌入到例如 F#、Rust 和 Go 中的功能性方式(通过元组)。在 C# 中有 很多 种方法可以做到这一点:
- Return 包含值和错误的元组,例如
(T? value, string? error)
- 使用值和错误创建一条记录
- 创建单独的 Success 和 Error 类 共享一个公共
IResult<T>
接口
模式匹配可以与任何选项一起使用来检索错误或值,而无需大量 if
语句。
假设我们有一个特定的错误类型,HttpError。:
record HttpError(HttpStatusCode Status,string Message);
使用元组,方法变为:
public async static Task<(T value,HttpError error> GetSomethingSpecialAsync<T>(this HttpClient client,string url)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
return (default,new HttpError(response.Status,....);
}
}
并调用:
var (value,error)=await client.GetSomethingSpecialAsync(url);
if(error!=null)
{
var (status,msg)=error;
Console.WriteLine($"Calling {url} Failed with {status}:{msg}");
...
}
我们可以创建 Result
记录来代替元组:
record Result<T>(T? Value,HttpError? Error);
或分开类:
interface IResult<T>
{
bool IsSuccess{get;}
}
record Success<T>(T Value):IResult<T>
{
public bool IsSuccess=>true;
}
record Error<T>(HttpError Error):IResult<T>
{
public bool IsSuccess => false;
}
public async static Task<IResult<T>> GetSomethingSpecialAsync<T>(this HttpClient client,string url){...}
var result=await client.GetSomethingSpecialAsync(url);
在所有情况下都可以使用模式匹配来简化对结果的处理,例如:
var result=await client.GetSomethingSpecialAsync<T>(url);
switch (result)
{
case Error<T> (status,message):
Console.WriteLine($"Calling {url} Failed with {Status}:{Message}");
break;
case Success<T> (value):
...
break;
}
具有特定的 Result<T>
或 IResult<T>
类型可以轻松编写通用方法来处理成功、错误或组成函数链。例如,如果前一个成功,则以下可用于调用“下一个”函数,否则只传播“错误”:
IResult<T> ThenIfOk(this IResult<T> previous,Func<T,IResult<T>> func)
{
return previous switch
{
Error<T> error=>error,
Success<T> ok=>func(ok.Value)
}
}
这将允许创建调用管道:
var finalResult=doSomething(url)
.ThenIfOk(value=>somethingElse(value))
.ThenIfOk(....);
这种风格被称为Railway oriented programming,在函数式和数据流(管道)编程中很常见
我正在尝试使用 EventHandler 扩展 HttpClient。 这可能吗?
我在 HttpClient 上有如下扩展:
public static class HttpClientExtensions
{
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
using var response = await client.GetAsync(url);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEvent(null, new HttpClientErrorEventArgs()
{
StatusCode = response.StatusCode,
Message = $"{response.StatusCode } {(int)response.StatusCode } "
});
return default(T);
}
response.EnsureSuccessStatusCode();
[... ]
}
}
public class HttpClientErrorEventArgs : EventArgs
{
public System.Net.HttpStatusCode StatusCode { get; set; }
public string Message { get; set; }
}
但是如何定义 HttpClientErrorEvent? 我尝试了以下但它不是特定 HttpClient 的扩展:
public static event EventHandler<HttpClientErrorEventArgs> HttpClientErrorEvent = delegate { };
您可以将处理程序存储在您的扩展程序中 class 并执行类似的操作吗?请注意此代码不是线程安全的,需要围绕字典和列表访问进行同步!
public static class HttpClientExtensions
{
private static Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>> Handlers { get; set; }
static HttpClientExtensions()
{
Handlers = new Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>>();
}
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
////code ....
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEventArgs args = null;
client.RaiseEvent(args);
return default(T);
////code
}
public static void AddHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (!found)
{
handlers = new List<Action<HttpClientErrorEventArgs>>();
Handlers[client] = handlers;
}
handlers.Add(handler);
}
public static void RemoveHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
handlers.Remove(handler);
if (handlers.Count == 0)
{
Handlers.Remove(client);
}
}
}
private static void RaiseEvent(this HttpClient client, HttpClientErrorEventArgs args)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
foreach (var handler in handlers)
{
handler.Invoke(args);
}
}
}
}
不要使用事件来 return 错误。对于初学者,您将如何确定哪个请求引发了哪个错误?您必须围绕每个调用注册和取消注册事件处理程序,但您将如何处理并发调用?您将如何编写多个此类调用?
错误无论如何都不是事件。充其量,您必须像处理回调一样处理事件 - 在这种情况下,为什么不使用实际的回调?
public async static Task<T> GetSomethingSpecialAsync<T>(this HttpClient client, string url,Action<(HttpStatusCode Status,string Message)> onError)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
onError(response.Status,....);
return default;
}
}
...
var value=await client.GetSomethingSpesialAsync(url,
(status,msg)=>{Console.WriteLine($"Calling {url} Failed with {status}:{msg}");}
);
async/await
的创建是为了让人们可以摆脱回调和事件。用事件组合多个异步调用几乎是不可能的,并且很难用回调来做到这一点。这就是为什么 很多 语言(C#、JavaScript、Dart,甚至在某种程度上是 C++)引入了 promises 和 async/await
来摆脱成功和错误回调。
您实际上可以 return 调用函数的结果或错误,而不是调用回调。这是一种嵌入到例如 F#、Rust 和 Go 中的功能性方式(通过元组)。在 C# 中有 很多 种方法可以做到这一点:
- Return 包含值和错误的元组,例如
(T? value, string? error)
- 使用值和错误创建一条记录
- 创建单独的 Success 和 Error 类 共享一个公共
IResult<T>
接口
模式匹配可以与任何选项一起使用来检索错误或值,而无需大量 if
语句。
假设我们有一个特定的错误类型,HttpError。:
record HttpError(HttpStatusCode Status,string Message);
使用元组,方法变为:
public async static Task<(T value,HttpError error> GetSomethingSpecialAsync<T>(this HttpClient client,string url)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
return (default,new HttpError(response.Status,....);
}
}
并调用:
var (value,error)=await client.GetSomethingSpecialAsync(url);
if(error!=null)
{
var (status,msg)=error;
Console.WriteLine($"Calling {url} Failed with {status}:{msg}");
...
}
我们可以创建 Result
记录来代替元组:
record Result<T>(T? Value,HttpError? Error);
或分开类:
interface IResult<T>
{
bool IsSuccess{get;}
}
record Success<T>(T Value):IResult<T>
{
public bool IsSuccess=>true;
}
record Error<T>(HttpError Error):IResult<T>
{
public bool IsSuccess => false;
}
public async static Task<IResult<T>> GetSomethingSpecialAsync<T>(this HttpClient client,string url){...}
var result=await client.GetSomethingSpecialAsync(url);
在所有情况下都可以使用模式匹配来简化对结果的处理,例如:
var result=await client.GetSomethingSpecialAsync<T>(url);
switch (result)
{
case Error<T> (status,message):
Console.WriteLine($"Calling {url} Failed with {Status}:{Message}");
break;
case Success<T> (value):
...
break;
}
具有特定的 Result<T>
或 IResult<T>
类型可以轻松编写通用方法来处理成功、错误或组成函数链。例如,如果前一个成功,则以下可用于调用“下一个”函数,否则只传播“错误”:
IResult<T> ThenIfOk(this IResult<T> previous,Func<T,IResult<T>> func)
{
return previous switch
{
Error<T> error=>error,
Success<T> ok=>func(ok.Value)
}
}
这将允许创建调用管道:
var finalResult=doSomething(url)
.ThenIfOk(value=>somethingElse(value))
.ThenIfOk(....);
这种风格被称为Railway oriented programming,在函数式和数据流(管道)编程中很常见