总体思路很简单:合并 P 和 Q,使用 BufferWithCount(2) 获取值对,然后根据您的逻辑处理对:
P.Merge(Q).BufferWithCount(2).Select(values =>
{
var first = values[0];
var second = values[1];
if (first is P && second is P ||
first is Q && second is Q)
{
return 0;
}
if (first is P)
{
return selector(first, second);
}
else // suppose Q, P is a valid sequence as well.
{
return selector(second, first);
}
});
现在困难的部分是合并 P 和 Q 如果它们是不同的类型,然后在 Select 中区分它们。如果它们属于同一类型,您可以使用类似 Enigmativity 提出的简单方法,即
var pqs =
(from p in ps select new { k = "p", v = p })
.Merge(from q in qs select new { k = "q", v = q });
现在困难的部分是如果它们是不同的类型,要合并它们,我们需要一些通用的包装器类型,例如来自 Haskell 的Data.Either :
public abstract class Either<TLeft, TRight>
{
private Either()
{
}
public static Either<TLeft, TRight> Create(TLeft value)
{
return new Left(value);
}
public static Either<TLeft, TRight> Create(TRight value)
{
return new Right(value);
}
public abstract TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight);
public sealed class Left : Either<TLeft, TRight>
{
public Left(TLeft value)
{
this.Value = value;
}
public TLeft Value
{
get;
private set;
}
public override TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight)
{
return onLeft(this.Value);
}
}
public sealed class Right : Either<TLeft, TRight>
{
public Right(TRight value)
{
this.Value = value;
}
public TRight Value
{
get;
private set;
}
public override TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight)
{
return onRight(this.Value);
}
}
}
有趣的是,在 System.Reactive.dll 中已经有类似的 Either 类,不幸的是它是内部的,所以我们需要自己的实现。现在我们可以将 P 和 Q 都放入 Either 并继续解决方案(我已经概括了一点,所以你可以返回任何结果而不是只返回 int ):
public static IObservable<TResult> SmartZip<TLeft, TRight, TResult>(
IObservable<TLeft> leftSource,
IObservable<TRight> rightSource,
Func<TLeft, TRight, TResult> selector)
{
return Observable
.Merge(
leftSource.Select(Either<TLeft, TRight>.Create),
rightSource.Select(Either<TLeft, TRight>.Create))
.BufferWithCount(2)
.Select(values =>
{
// this case was not covered in your question,
// but I've added it for the sake of completeness.
if (values.Count < 2)
{
return default(TResult);
}
var first = values[0];
var second = values[1];
// pattern-matching in C# is really ugly.
return first.Match(
left => second.Match(
_ => default(TResult),
right => selector(left, right)),
right => second.Match(
left => selector(left, right),
_ => default(TResult)));
});
}
这是一个关于所有这些可怕丑陋东西的小演示。
private static void Main(string[] args)
{
var psource = Observable
.Generate(1, i => i < 100, i => i, i => i + 1)
.Zip(Observable.Interval(TimeSpan.FromMilliseconds(10.0)), (i, _) => i);
var qsource = Observable
.Generate(1, i => i < 100, i => (double)i * i, i => i + 1)
.Zip(Observable.Interval(TimeSpan.FromMilliseconds(30.0)), (i, _) => i);
var result = SmartZip(
psource,
qsource,
(p, q) => q / p).ToEnumerable();
foreach (var item in result)
{
Console.WriteLine(item);
}
}