如何不复制代码?通用和非通用实现 – 使用相同的代码体。

huangapple go评论59阅读模式
英文:

how to not copy code ? generic and non generic implementation - to use same body

问题

请告诉我如何重构这段代码,以便减少复制/粘贴的部分?是否有一种方法可以将通用代码调用为非通用代码?我应该将<object>传递给通用代码并满足这一点吗?什么是最佳解决方案?

例如,我有以下代码:

public interface IHttpService
{
    Task<T> Post<T>(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default);
    Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default);
}

它们几乎相同,但实现不同。

public async Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
    await sendRequest(request, timeOut, token);
}

public async Task<T> Post<T>(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
{
    var request = a new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
    return await sendRequest<T>(request, timeOut, token);
}

私下定义的sendRequest方法如下:

private async Task sendRequest(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);

        if (!response.IsSuccessStatusCode)
        {              
            var error = await response.Content.ReadAsStringAsync(token);
            throw new Exception(error);             
        }     
    }
}

private async Task<T> sendRequest<T>(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);      
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
        
        if (!response.IsSuccessStatusCode)
        {      
            var error = await response.Content.ReadAsStringAsync(token);
            throw new Exception(error);
        }
        return (await response.Content.ReadFromJsonAsync<T>())!;
    }
}

在更新的部分,sendRequest方法的返回类型已更改:

private async Task<HttpContent> sendRequest(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
     
        if (!response.IsSuccessStatusCode)
        {
             await response.Content.ReadAsStringAsync(token);
             throw new Exception(error);
        }

        return response.Content;
    }
    catch (AccessTokenNotAvailableException)
    {
        _navigationManager.NavigateToLogout(StaticStringHelper.LogoutPatch);
        return default(HttpContent)!;
    }
}

当像下面这样使用它时:

public async Task<T> Get<T>(string uri, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Get, uri);        
    return await (await sendRequest(request, timeOut, token)).ReadFromJsonAsync<T>(cancellationToken: token);
}

你会遇到异常:

microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot access a disposed object.
Object name: 'System.Net.Http.BrowserHttpContent'.
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.BrowserHttpContent'.
at System.Net.Http.HttpContent.CheckDisposed()
at System.Net.Http.HttpContent.ReadAsStreamAsync(CancellationToken cancellationToken)

这个异常通常表示HttpContent对象已被释放。可能需要检查代码中是否有其他地方错误地释放了HttpContent对象。

英文:

please tell me how should i rework this code so it is not so much copy/paste? is there any way to call generic as nongeneric? should i pass &lt;object&gt; to generic and be happy with that? what is best solution ?

i have for example

public interface IHttpService
{
 Task&lt;T&gt; Post&lt;T&gt;(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default);
 Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default);
 }

so they have almost same but diferent implementation

 public async Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
    await sendRequest(request, timeOut, token);
}

public async Task&lt;T&gt; Post&lt;T&gt;(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
    return await sendRequest&lt;T&gt;(request, timeOut, token);
}


private async Task sendRequest(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);

        if (!response.IsSuccessStatusCode)
        {              
                var error = await response.Content.ReadAsStringAsync(token);
                throw new Exception(error);             
        }     
}

private async Task&lt;T&gt; sendRequest&lt;T&gt;(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);      
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
        
        if (!response.IsSuccessStatusCode)
        {      
                var error = await response.Content.ReadAsStringAsync(token);
                throw new Exception(error);
            
        }
        return (await response.Content.ReadFromJsonAsync&lt;T&gt;())!;

}

update

private async Task&lt;HttpContent&gt; sendRequest(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
    try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
     
        if (!response.IsSuccessStatusCode)
        {
             await response.Content.ReadAsStringAsync(token);
                throw new Exception(error);
            
        }

        return response.Content;

    }
    catch (AccessTokenNotAvailableException)
    {
        _navigationManager.NavigateToLogout(StaticStringHelper.LogoutPatch);
        return default(HttpContent)!;
    }
}

and when i use it like

 public async Task&lt;T&gt; Get&lt;T&gt;(string uri, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Get, uri);        
    return  await (await sendRequest(request, timeOut, token)).ReadFromJsonAsync&lt;T&gt;(cancellationToken: token);

}

i get

 microsoft.AspNetCore. Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
  Unhandled exception rendering component: Cannot access a disposed object.
  Object name: &#39;System.Net.Http.BrowserHttpContent&#39;.
System. ObjectDisposedException: Cannot access a disposed object.
Object name: &#39;System.Net.Http.BrowserHttpContent&#39;.
 at System.Net.Http.HttpContent.CheckDisposed()
 at System.Net.Http.HttpContent.ReadAsStreamAsync(CancellationToken cancellationToken)

答案1

得分: 1

C# 中没有通用的 "void"。在某些情况下,你可以只是使用一个带有像 int 或 bool 这样的无意义值的通用类型。在其他情况下,你只需要接受它并保留两个实现。

在这种特定情况下,唯一的区别似乎是 response.Content.ReadFromJsonAsync<T>(),因此你可以重构它,使所有其他代码都通用。由于内容已被处理,你需要使用委托来注入不同的代码:

 public Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
=> Post<int>(uri, content, c => Task.FromResult(0), timeOut , token);

public async Task<T> Post<T>(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
=> Post(uri, content, ReadFromJson, timeOut , token)!;

private Task<T> ReadFromJson(HttpContent content) => content.ReadFromJsonAsync<T>();

private async Task<T> Post<T>(string uri, HttpContent content, Func<HttpContent, Task<T>> handleContent, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
        try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);      
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
        
        if (!response.IsSuccessStatusCode)
        {      
                var error = await response.Content.ReadAsStringAsync(token);
                throw new Exception(error);
            
        }
        return await handleContent(response.Content);
}

c => Task.FromResult(0) 只是为了使通用类型适配,'0' 值从未在任何地方使用。

英文:

There is no generic "void" in c#. In some cases you can just use a generic with something like a int or bool as a meaningless value. In other cases you just have to suck it up and keep two implementations.

In this particular case the only difference seem to be response.Content.ReadFromJsonAsync&lt;T&gt;(), so you should be able to refactor this to make all other code common. Since the content is disposed you need to use a delegate to inject the code that is different:

 public Task Post(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
=&gt; Post&lt;int&gt;(uri, content, c =&gt; Task.FromResult(0), timeOut , token);

public async Task&lt;T&gt; Post&lt;T&gt;(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
=&gt; Post(uri, content, ReadFromJson, timeOut , token)!;

private Task&lt;T&gt; ReadFromJson(HttpContent content) =&gt; content.ReadFromJsonAsync&lt;T&gt;();

private async Task&lt;T&gt; Post&lt;T&gt;(string uri, HttpContent content, Func&lt;HttpContent, Task&lt;T&gt;&gt; handleContent, int timeOut = 100, CancellationToken token = default)
{
    var request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = content;
        try
    {
        using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);      
        using var response = await _httpClient.SendAsync(request, linkedCts.Token);
        
        if (!response.IsSuccessStatusCode)
        {      
                var error = await response.Content.ReadAsStringAsync(token);
                throw new Exception(error);
            
        }
        return await handleContent(response.Content);
}

c =&gt; Task.FromResult(0) is just something to make the generic types fit, the '0' value never used anywhere.

huangapple
  • 本文由 发表于 2023年3月7日 15:05:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75658873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定