2

当断点似乎神奇地出现在枚举器内的同一位置两次时,我只是花了一些时间挠头。

事实证明,这个错误是一个直接的疏忽:

    protected override void Extract()
    {
        LogGettingOffers();
        var offerIds = CakeMarketingUtility.OfferIds(advertiserId);
        LogExtractingClicks(offerIds);
        foreach (var offerId in offerIds)
        {
            int rowCount;
            var clicks = RetryUtility.Retry(3, 10000, new[] { typeof(Exception) }, () =>
            {
                return CakeMarketingUtility.EnumerateClicks(dateRange, advertiserId, offerId);
            });
            foreach (var clickBatch in clicks.InBatches(1000))
            {
                LogExtractedClicks(offerId, clickBatch);

                // SHOULD BE clickBatch, NOT clicks
                Add(clicks);
            }
        }
        End();
    }

这让我想知道一个人可能会采取什么(如果有的话)预防措施来编写捕获这样的错误的代码。

请注意,我并不肯定沿着这条思路走下去是有意义的——也许答案是“不要编写不正确的代码”,我愿意接受。

这是产生结果的实际代码:

    public static IEnumerable<Click> EnumerateClicks(DateRange dateRange, int advertiserId, int offerId)
    {
        // initialize to start at the first row
        int startAtRow = 1;

        // hard code an upper limit for the max number of rows to be returned in one call
        int rowLimitForOneCall = 5000;

        bool done = false;
        int total = 0;
        while (!done)
        {
            Logger.Info("Extracted a total of {0} rows, checking for more, starting at row {1}..", total, startAtRow);

            // prepare the request
            var request = new ClicksRequest
            {
                start_date = dateRange.FromDate.ToString("MM/dd/yyyy"),
                end_date = dateRange.ToDate.ToString("MM/dd/yyyy"),
                advertiser_id = advertiserId,
                offer_id = offerId,
                row_limit = rowLimitForOneCall,
                start_at_row = startAtRow
            };

            // create the client, call the service and check the response
            var client = new ClicksClient();
            var response = client.Clicks(request);
            if (!response.Success)
            {
                throw new Exception("ClicksClient failed");
            }

            // update the running total
            total += response.RowCount;

            // return result
            foreach (var click in response.Clicks)
                yield return click;

            // update stopping condition for loop
            done = (response.RowCount < rowLimitForOneCall);

            // increment start row for next iteration
            startAtRow += rowLimitForOneCall;
        }

        Logger.Info("Extracted a total of {0}, done.", total);
    }
4

2 回答 2

1

对于这个特定问题,我会说解决方案是“不要编写错误的代码”。尤其是当可以在不改变任何状态的情况下生成结果时(比如从列表中枚举元素时),我认为从任何可枚举中创建多个枚举器应该是可以的。

你可以创建一个IEnumerable包装器来确保GetEnumerator只调用一次,但是如果你真的需要调用它两次呢?您真正想要的是捕获错误,而不是捕获被枚举多次的可枚举项,这不是您可以轻松放入软件解决方案的东西。

也许问题是那个clickBatch并且clicks具有相同的类型,所以编译器无法区分两者。

于 2013-07-17T18:54:53.423 回答
1

有时我需要确保我公开的枚举只被调用一次。例如:返回我只有一次读取可用的流式信息,或者非常昂贵的查询。

尝试以下扩展类:

public static class Extensions
{
    public static IEnumerable<T> SingleEnumeration<T>(this IEnumerable<T> source)
    {
        return new SingleEnumerator<T>(source);
    }
}

public class SingleEnumerator<T> : IEnumerable<T>
{
    public SingleEnumerator(IEnumerable<T> source)
    {
        this.source = source;
    }

    public IEnumerator<T> GetEnumerator()
    {
        // return an empty stream if called twice (or throw)
        if (source == null)
            return (new T[0]).AsEnumerable().GetEnumerator();

        // return the actual stream
        var result =source.GetEnumerator();
        source = null;
        return result;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        // return an empty stream if called twice (or throw)
        if (source == null)
            return (new T[0]).AsEnumerable().GetEnumerator();

        var result = source.GetEnumerator();
        source = null;
        return result;
    }

    private IEnumerable<T> source;
}
于 2014-04-06T19:05:25.817 回答