我正在寻找有关如何处理以下情况的建议。
我正在创建尝试获取某些数据的方法,遵循以下模式:
// Typical pattern
public bool TryBlah(string key, out object value)
{
// ... set value and return boolean
}
我尝试在异步版本上遵循此模式时遇到问题,因为您无法在异步方法上使用out
:
// Ideal async pattern (not allowed to use an 'out' parameter, so this fails)
public async Task<bool> TryBlah(string key, out object value)
{
// ... set value, perform some slow io operation, return bool
}
一种解决方法是返回包含数据的元组。这适用于返回单个数据类型的方法,如下所示:
// Tuple version
public async Task<Tuple<bool, object>> TryBlah(string key)
{
// ... perform some slow io, return new Tuple<bool, object>(...)
}
问题在于您想要返回不同的数据类型。如果不使用异步,您可以创建几个具有几乎相同签名的方法,如下所示:
public bool TryBlah(string key, out byte[] value)
{
// ...
}
public bool TryBlah(string key, out string value)
{
// ...
}
那很好。这就是我想要做的。这个api非常直接且易于使用(方法名称都相同,只有传入的数据发生变化)。
无法使用异步方法使用out
会让人感到困惑。
解决此问题的一种方法是返回Tuple
个数据。但是现在你不能拥有几乎相同的方法签名,如下所示:
// The suck... the signatures match, but you want to return different values.
// You can't do this:
public async Task<Tuple<bool, byte[]>> TryBlah(string key)
{
// ...
}
public async Task<Tuple<bool, string>> TryBlah(string key)
{
// ...
}
这些方法失败,因为它们具有相同的签名。想到解决这个问题的唯一方法是给每个方法一个不同的名称,如:
public async Task<Tuple<bool, byte[]>> TryBlahByteArray(string key)
{
// ...
}
public async Task<Tuple<bool, string>> TryBlahString(string key)
{
// ...
}
我的问题是,这现在创造了我认为讨厌的api,你现在有很多不同的方法。是的,这不是一个大问题,但我觉得必须有更好的方法。
在使用像这样的异步方法时,是否还有其他模式可以使用更好的api?我愿意接受任何建议。
答案 0 :(得分:9)
也许您可以使用Action<T>
作为外部替代
示例:
public async Task<bool> TryBlah(string key, Action<int> value)
{
int something = await DoLongRunningIO();
value(something)
return true;
}
用法:
int myOutParam = 0;
if (await TryBlah("Something", value => myOutParam = value))
{
// do somthing
}
答案 1 :(得分:2)
听起来像是泛型的问题。
public async Task<Tuple<bool, TResult>> TryBlah<TResult>(string key)
{
var resultType = typeof(TResult);
// ... perform some slow io, return new Tuple<bool, TResult>(...)
}
答案 2 :(得分:2)
这是一个大约2017年更新ValueTuples,你的糟糕选择并不是那么糟糕。
public async Task<(bool, byte[])> TryBlahByteArray(string key)
{
// await something
return (true, new byte[1]);
}
public async Task<(bool, string)> TryBlahString(string key)
{
// await something
return (false, "blah");
}
用作
(bool success, byte[] blahs) = await TryBlahByteArray("key");
并且
(bool success, string blah) = await TryBlahString("key");
我不经常想要相同的方法名称返回不同的东西或原始的object
,所以也许这不是一个问题。您的里程可能会有所不同。
答案 3 :(得分:1)
我不会在TPL中使用Try *方法。而是使用带有OnlyOnFaulted选项的continuation(Task.ContinueWith)。
这样你的任务就会以这种或那种方式完成,调用者可以决定如何处理错误,取消等等。
它也摆脱了元组。
至于其他设计问题,每当我看到有人说“我希望这种方法根据返回类型重载”时,我闻到了一个坏主意。我宁愿看到详细的名称(GetString,GetByte,GetByteArray等 - 看看SqlDataReader)或者让API返回一个非常基本的类型(例如,byte [] - 看看Stream)并让调用者创建更高级别的转换,比如StreamReader /的TextReader /等
答案 4 :(得分:0)
看起来您正在尝试创建一个接受请求的API,然后检索一些数据,然后以特定方式处理/转换该数据并将其返回给调用者。如果您实现了一个可以处理不同处理方法的管理器,该怎么办?
我提出了一个创建请求和响应类的解决方案,该类将传递给管理器,然后管理器在处理完成后返回结果。
public class Request
{
public Type ReturnType;
public string Key { get; set; }
public Request(string Key, Type returnType)
{
this.Key = Key;
this.ReturnType = returnType;
}
}
public class Response
{
public object value;
public Type returnType;
}
//Singleton processor to get data out of cache
public class CacheProcessor
{
private static CacheProcessor instance;
public static CacheProcessor Process
{
get
{
if (instance == null)
instance = new CacheProcessor();
return instance;
}
}
private Dictionary<Type, Func<Request, object>> Processors = new Dictionary<Type, Func<Request, object>>();
private CacheProcessor()
{
CreateAvailableProcessors();
}
//All available processors available here
//You could change type to string or some other type
//to extend if you need something like "CrazyZipUtility" as a processor
private void CreateAvailableProcessors()
{
Processors.Add(typeof(string), ProcessString);
Processors.Add(typeof(byte[]), ProcessByteArry);
}
//Fake method, this should encapsulate all crazy
//cache code to retrieve stuff out
private static string CacheGetKey(string p)
{
return "1dimd09823f02mf23f23f0"; //Bullshit data
}
//The goood old tryBlah... So Sexy
public Response TryBlah(Request request)
{
if (Processors.ContainsKey(request.ReturnType))
{
object processedObject = Processors[request.ReturnType].Invoke(request);
return new Response()
{
returnType = request.ReturnType,
value = processedObject
};
}
return null;
}
//Maybe put these in their own class along with the dictionary
//So you can maintain them in their own file
private static object ProcessString(Request request)
{
var value = CacheGetKey(request.Key);
//Do some shit
return value;
}
private static object ProcessByteArry(Request request)
{
var value = CacheGetKey(request.Key);
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] bytes = encoding.GetBytes(value);
return bytes;
}
}
最重要的是Dictionary(或HashSet)保存您可用的处理器。然后根据类型调用正确的处理器并返回结果。
将按如下方式调用代码。
var makeByteRequest = new Request("SomeValue", typeof(byte[]));
Response btyeResponse = CacheProcessor.Process.TryBlah(makeByteRequest);