C# 可以将回调调用转换为IEnumerable

C# 可以将回调调用转换为IEnumerable,c#,callback,enumerable,C#,Callback,Enumerable,我正在围绕第三方库编写一个包装器,它有一个方法来扫描它管理的数据。该方法接受一个回调方法,它为找到的数据中的每个项调用该方法 e、 g.该方法本质上是:void扫描(动作回调) 我想包装它并公开一个类似IEnumerable Scan()的方法 如果不使用单独的线程来执行实际扫描和缓冲区,这是可能的吗?请查看关键字--它将允许您拥有一个看起来像IEnumerable但实际上对每个返回值进行处理的方法。这个方法如何: IEnumerable<Object> Scan() { L

我正在围绕第三方库编写一个包装器,它有一个方法来扫描它管理的数据。该方法接受一个回调方法,它为找到的数据中的每个项调用该方法

e、 g.该方法本质上是:
void扫描(动作回调)

我想包装它并公开一个类似
IEnumerable Scan()的方法

如果不使用单独的线程来执行实际扫描和缓冲区,这是可能的吗?

请查看关键字--它将允许您拥有一个看起来像
IEnumerable
但实际上对每个返回值进行处理的方法。

这个方法如何:

IEnumerable<Object> Scan()
{
    List<Object> objList = new List<Object>();

    Action<Object> action = (obj) => { objList.Add(obj); };

    Scan(action);

    return objList;
}
IEnumerable Scan()
{
列表对象列表=新列表();
Action Action=(obj)=>{objList.Add(obj);};
扫描(动作);
返回对象列表;
}

您应该调查-这允许事件源作为
IEnumerable
使用


我不确定它是否允许普通回调这样显示(它针对.NET事件),但值得一看,因为它应该可以将常规回调显示为可观察的
IObservable

,您可以简单地使用反应式:

class Program
{
    static void Main(string[] args)
    {
        foreach (var x in CallBackToEnumerable<int>(Scan))
            Console.WriteLine(x);
    }

    static IEnumerable<T> CallBackToEnumerable<T>(Action<Action<T>> functionReceivingCallback)
    {
        return Observable.Create<T>(o =>
        {
            // Schedule this onto another thread, otherwise it will block:
            Scheduler.Later.Schedule(() =>
            {
                functionReceivingCallback(o.OnNext);
                o.OnCompleted();
            });

            return () => { };
        }).ToEnumerable();
    }

    public static void Scan(Action<int> act)
    {
        for (int i = 0; i < 100; i++)
        {
            // Delay to prove this is working asynchronously.
            Thread.Sleep(100);
            act(i);
        }
    }
}
类程序
{
静态void Main(字符串[]参数)
{
foreach(CallBackToEnumerable(扫描)中的var x)
控制台写入线(x);
}
静态IEnumerable CallBackToEnumerable(操作函数ReceivingCallback)
{
返回可观察的。创建(o=>
{
//将此线程调度到另一个线程,否则它将阻塞:
Scheduler.Later.Schedule(()=>
{
函数ReceivingCallback(o.OnNext);
o、 未完成();
});
return()=>{};
}).ToEnumerable();
}
公共静态无效扫描(行动法案)
{
对于(int i=0;i<100;i++)
{
//延迟以证明这是异步工作的。
睡眠(100);
第(i)款;
}
}
}

请记住,这并不考虑取消之类的事情,因为回调方法实际上不允许这样做。正确的解决方案需要对外部库的一部分进行操作。

这里有一个阻塞枚举器(扫描方法需要在单独的线程中运行)

公共类MyEnumerator:IEnumerator
{
私有只读队列_Queue=new Queue();
private ManualResetEvent _事件=新的ManualResetEvent(错误);
公共无效回调(对象值)
{
锁定(_队列)
{
_queue.Enqueue(值);
_event.Set();
}
}
公共空间处置()
{
}
公共图书馆
{
_event.WaitOne();
锁定(_队列)
{
当前=_queue.Dequeue();
如果(_queue.Count==0)
_event.Reset();
}
返回true;
}
公共无效重置()
{
_queue.Clear();
}
公共对象当前{get;private set;}
对象IEnumerator.Current
{
获取{返回当前;}
}
}
静态void Main(字符串[]参数)
{
var枚举器=新的MyEnumerator();
扫描(枚举器.回调);
while(枚举数.MoveNext())
{
Console.WriteLine(枚举器.Current);
}
}

您可以将它包装成一个简单的
IEnumerable
,但我不推荐它
IEnumerable
list意味着可以在同一个列表上运行多个枚举数,而在本例中您不能这样做。

是的,但我认为OP想知道如何将这样一个方法连接到
Action
回调。哦,我明白您的意思了——这是一个更难的问题!是的,我最初的想法是一个单行方法
Scan(x=>{yield-returnx;})
,但不幸的是,yield不是这样工作的。
扫描(操作)
可能会添加项目,而返回的
IEnumerable
将被迭代,导致异常,因为回调将执行异步。是的,这是可行的,但回调的潜在数量可能达到数百万,所以我不希望在迭代之前把所有的数据都缓冲起来。你能概述一下你的第三方库的确切流程吗?听起来好像它已经生成了多个线程。还要记住,您可能有一个多播代理。还有,你是如何确定扫描完成的?老实说,我看不出这样做有什么价值。如果你想做
foreach(objectobj在scanable.Scan()中){/*用obj*/}
,那真的比
scanable.Scan(obj=>{/*用obj*/}做点什么好吗
?@weismat:不,它不是衍生线程,它基本上是在内部对其数据存储进行迭代,并对每个记录调用回调。@Flyn1179:是的,这是可行的,但我正在编写一个包装器供其他人使用,所以希望它尽可能明显……嗯,这会很好地工作。我希望避免使用单独的线程,但似乎不可能只使用一个线程。这似乎是最简单的方法。谢谢。是的,我认为实际上不可能只用一个线程就把它放入一个可枚举列表中。我能得到的最接近的是一个可观察的,但在这种情况下,它只是另一个名字的回调:)
    public class MyEnumerator : IEnumerator<object>
    {
        private readonly Queue<object> _queue = new Queue<object>();
        private ManualResetEvent _event = new ManualResetEvent(false);

        public void Callback(object value)
        {
            lock (_queue)
            {
                _queue.Enqueue(value);
                _event.Set();
            }
        }

        public void Dispose()
        {

        }

        public bool MoveNext()
        {
            _event.WaitOne();
            lock (_queue)
            {
                Current = _queue.Dequeue();
                if (_queue.Count == 0)
                    _event.Reset();
            }
            return true;
        }

        public void Reset()
        {
            _queue.Clear();
        }

        public object Current { get; private set; }

        object IEnumerator.Current
        {
            get { return Current; }
        }
    }

    static void Main(string[] args)
    {
        var enumerator = new MyEnumerator();
        Scan(enumerator.Callback);

        while (enumerator.MoveNext())
        {
            Console.WriteLine(enumerator.Current);
        }
    }