C# 可观测流上转换链(管道)的正确模式?
我有以下情况:C# 可观测流上转换链(管道)的正确模式?,c#,reactive-programming,C#,Reactive Programming,我有以下情况: 给定一个对象流IObservable 处理每个E以获得E1或错误状态,在这种情况下,我需要错误消息M1 处理每个E1以获取E2或错误消息M2 还有一个额外的复杂性,即结果En和/或错误消息Mn可能取决于所有值E,E1,…,En-1——而不仅仅取决于En-1 考虑到所有这些,有没有比我使用的更好的模式 [编辑]根据要求,我添加了一个完全有效的示例;不幸的是,这使得这篇文章相当大 internal class Program { private static void Ma
- 给定一个对象流
IObservable
- 处理每个
以获得E
或错误状态,在这种情况下,我需要错误消息E1
M1
- 处理每个
以获取E1
或错误消息E2
M2
Mn
可能取决于所有值E
,E1
,…,En-1
——而不仅仅取决于En-1
考虑到所有这些,有没有比我使用的更好的模式
[编辑]根据要求,我添加了一个完全有效的示例;不幸的是,这使得这篇文章相当大
internal class Program
{
private static void Main()
{
var stream = Enumerable.Range(1, 10).Select(i => new Record { Id = i }).ToObservable();
stream
.Select(it => new ComplexType { Item = it })
.SelectIfOk(Process1)
.SelectIfOk(Process2)
.SelectIfOk(ProcessN)
.Subscribe(DisplayResult);
Console.ReadLine();
}
private static ComplexType Process1(ComplexType data)
{
// do some processing
data.E1 = data.Item.Id * 10;
// check for errors in output
if (data.E1 == 30 || data.E1 == 70)
{
data.Errors.Add("Error");
}
return data;
}
private static ComplexType Process2(ComplexType data)
{
// do some processing
data.E2 = (data.E1 - 3).ToString();
// check for errors in output
// can generate multiple errors for the same item
if (data.E2.StartsWith("4"))
{
// does not only depend on the immediate precursor, E1 in this case
data.Errors.Add("Starts with 4 -- " + data.Item.Id);
}
if (data.E2.StartsWith("8"))
{
data.Errors.Add("Starts with 8");
}
return data;
}
private static ComplexType ProcessN(ComplexType data)
{
// do some processing
data.EN = "Success " + data.E2;
// this one doesn't generate errors
return data;
}
private static void DisplayResult(ComplexType data)
{
if (data.Errors.Any())
{
Console.WriteLine("{0:##0} has errors: " + string.Join(",", data.Errors));
}
else
{
Console.WriteLine("{0:##0}: {1}", data.Item.Id, data.EN);
}
}
}
以下是上述代码示例中使用的类:
public class Record
{
public int Id { get; set; }
public string FullName { get; set; }
public string OtherStuff { get; set; }
}
public class ComplexType
{
public Record Item { get; set; }
// intermediary results
public int E1 { get; set; }
public string E2 { get; set; }
// final result
public string EN { get; set; }
public List<string> Errors { get; set; }
public ComplexType()
{
Errors = new List<string>();
}
}
运行此代码的结果是:
1: Success 7
2: Success 17
3 has errors: Error
4: Success 37
5 has errors: Starts with 4 -- 5
6: Success 57
7 has errors: Error
8: Success 77
9 has errors: Starts with 8
10: Success 97
我正在使用ComplexType,这样我就可以同时携带中间结果和错误状态,而且看起来。。。可疑的。我已经盯着那个代码看了一周了(这是为了一个爱好项目),我一直觉得我错过了用Rx做事情的正确方法
[编辑]我忘了提到一件非常重要的事情:我必须处理流中的所有项目,即使其中一些项目会产生错误;这就是为什么我不能只使用发生异常的Subscribe
重载-它将完成流。出现错误时放弃一项是可以的(如果Process1
生成错误,则Process2
,…,ProcessN
将不再执行),但不放弃整个流
[编辑]另一个澄清:如果有帮助的话,我想到的处理将更自然地适用于TPL数据流库,但我仅限于.NET 4.0,因此无法使用它
顺便说一句,我在Rx中找不到任何关于错误处理的严肃讨论,通常会提到
Subscribe
重载/OnError调用,仅此而已。有人推荐深入研究该主题吗?提前了解所有ProcessN
意味着您可以摆脱多个Select
操作符,简化查询并揭示您的真实意图
stream.Select(e => Process(e));
...
? Process(E e)
{
// Process1, Process2, ...ProcessN
}
现在我们看到这不是一个反应性问题。这更像是一个交互式聚合问题
您还没有提到您最终需要在订阅中观察哪种输出,尽管我假设这是过程的聚合结果
要定义返回类型,我们首先需要定义ProcessN
的返回类型。现在,我将使用语义更好的类型,而不是使用您的ComplexType
:
Either<E, Exception> Process(?);
现在我们可以定义聚合器的返回类型(定义见上文):
IList进程(E)
{
//进程1,进程2,…进程n
}
聚合器的主体可以按如下方式实现:
IList<Either<E, Exception>> Process(E e)
{
var results = new List<Either<E, Exception>>();
results.Add(Process1(results.AsReadOnly()));
results.Add(Process2(results.AsReadOnly()));
...
results.Add(ProcessN(results.AsReadOnly()));
return results.AsReadOnly();
}
IList进程(E)
{
var results=新列表();
results.Add(Process1(results.AsReadOnly());
results.Add(Process2(results.AsReadOnly());
...
results.Add(ProcessN(results.AsReadOnly());
返回结果。AsReadOnly();
}
接收中的错误处理
从阅读文章开始
有一个关于错误处理操作符的部分
以下是关于Rx中错误处理语义和契约的一些深入讨论/评论:
(完全披露:我参与了所有这些特定的讨论。)当我问这个问题时,我不知道Rx中的
通知类和物化方法。下面是我提出的解决方案-它主要解决了问题的“管道”方面,但我可以通过使用元组解决“依赖于中间结果”方面:
private static void Main()
{
var source = new Subject<int>();
source
.Materialize()
.SelectIfOk(Process1)
.SelectIfOk(Process2)
.Subscribe(it =>
Console.WriteLine(it.HasValue
? it.Value.ToString()
: it.Exception != null ? it.Exception.Message : "Completed."));
source.OnNext(1);
source.OnNext(2);
source.OnNext(3);
source.OnNext(4);
source.OnNext(5);
source.OnCompleted();
Console.ReadLine();
}
private static int Process1(int value)
{
if (value == 3)
throw new Exception("error 1");
// do some processing
return value * 2;
}
private static string Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return value + " good";
}
private static IObservable<Notification<TR>> SelectIfOk<T, TR>(this IObservable<Notification<T>> stream,
Func<T, TR> selector)
{
Func<T, Notification<TR>> trySelector = it =>
{
try
{
var value = selector(it);
return Notification.CreateOnNext(value);
}
catch (Exception ex)
{
return Notification.CreateOnError<TR>(ex);
}
};
return stream.Select(it =>
it.HasValue
? trySelector(it.Value)
: it.Exception != null
? Notification.CreateOnError<TR>(it.Exception)
: Notification.CreateOnCompleted<TR>());
}
private static void Main()
{
var source=新主题();
来源
.具体化
。选择确定(进程1)
。选择确定(进程2)
.订阅(it=>
Console.WriteLine(it.HasValue
?it.Value.ToString()
:it.Exception!=null?it.Exception.Message:“已完成”。);
资料来源.OnNext(1);
资料来源:OnNext(2);
资料来源:OnNext(3);
资料来源:OnNext(4);
资料来源:OnNext(5);
source.OnCompleted();
Console.ReadLine();
}
私有静态int进程1(int值)
{
如果(值==3)
抛出新异常(“错误1”);
//做一些处理
返回值*2;
}
私有静态字符串Process2(int值)
{
如果(值==4)
抛出新异常(“错误2”);
//做一些处理
返回值+良好;
}
私有静态IObservable SelectIfOk(此IObservable流,
Func选择器)
{
Func trySelector=it=>
{
尝试
{
var值=选择器(it);
返回通知.CreateOnNext(值);
}
捕获(例外情况除外)
{
返回通知。CreateOnError(ex);
}
};
返回流。选择(它=>
它有价值
?trySelector(it.值)
:it.Exception!=null
?Notification.CreateOnError(it.Exception)
:Notification.CreateOnCompleted());
}
如果我想使用中间结果,那么,正如我所说,我将使用元组:
private static Tuple<int, string> Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return Tuple.Create(value, value * 3 + " good");
}
private static string Process3(Tuple<int, string> value)
{
return value.Item1 + " -> " + value.Item2;
}
私有静态元组进程2(int值)
{
如果(值==4)
抛出新异常(“错误2”);
//做一些处理
返回Tuple.Create(value,value*3+“good”);
}
私有静态字符串Process3(元组值)
{
返回value.Item1+“->”+value.Item2;
}
(我需要将。选择ifok(Process3)
添加到管道中。)
我不愿意将自己的答案标记为正确,所以我将暂时不讨论这个问题;然而,据我所知,它确实满足了我的要求。您提前知道所有流程吗?或者您是否因为Select运算符的惰性而需要它?我提前知道所有ProcessN;我使用Select是因为它需要
IList<Either<E, Exception>> Process(E e)
{
var results = new List<Either<E, Exception>>();
results.Add(Process1(results.AsReadOnly()));
results.Add(Process2(results.AsReadOnly()));
...
results.Add(ProcessN(results.AsReadOnly()));
return results.AsReadOnly();
}
private static void Main()
{
var source = new Subject<int>();
source
.Materialize()
.SelectIfOk(Process1)
.SelectIfOk(Process2)
.Subscribe(it =>
Console.WriteLine(it.HasValue
? it.Value.ToString()
: it.Exception != null ? it.Exception.Message : "Completed."));
source.OnNext(1);
source.OnNext(2);
source.OnNext(3);
source.OnNext(4);
source.OnNext(5);
source.OnCompleted();
Console.ReadLine();
}
private static int Process1(int value)
{
if (value == 3)
throw new Exception("error 1");
// do some processing
return value * 2;
}
private static string Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return value + " good";
}
private static IObservable<Notification<TR>> SelectIfOk<T, TR>(this IObservable<Notification<T>> stream,
Func<T, TR> selector)
{
Func<T, Notification<TR>> trySelector = it =>
{
try
{
var value = selector(it);
return Notification.CreateOnNext(value);
}
catch (Exception ex)
{
return Notification.CreateOnError<TR>(ex);
}
};
return stream.Select(it =>
it.HasValue
? trySelector(it.Value)
: it.Exception != null
? Notification.CreateOnError<TR>(it.Exception)
: Notification.CreateOnCompleted<TR>());
}
private static Tuple<int, string> Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return Tuple.Create(value, value * 3 + " good");
}
private static string Process3(Tuple<int, string> value)
{
return value.Item1 + " -> " + value.Item2;
}