【问题标题】:c# do while functional equivalent?c# do while 功能等效?
【发布时间】:2016-10-06 05:34:30
【问题描述】:

我正在编写一个控制台应用程序,它可以调用 web 服务并接收返回的 XML,其中 XML 将是“文件夹”或“文档”的条目。我在 C# 中编写了以下 do while 循环来循环调用 web 服务,但我想知道是否有更实用的方法来做到这一点:

 ...
        var documentUrls = new Dictionary<string, string>();

        // get the root folder's children
        var rootFeed = $"{baseUrl}/biprws/infostore/{23}/children"
            .Do(str => Console.WriteLine($"{str}:"))
            .GetResponse(headerToken)
            .GetXmlString()
            .DeserializeFromXml<Library.Children.Feed>();

        var folderUrls = rootFeed.Entries
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "folder"))
            .Select(entry => $"{entry.Link.Href}/children")
            .ToList();

        // go through each folder and pull out the documents - if a nested folder is found then check that too
        do
        {
            var feeds = folderUrls
                .Select(url => url.GetResponse(headerToken).GetXmlString().DeserializeFromXml<Library.Children.Feed>())
                .SelectMany(x => x.Entries);

            var folders = feeds
                .Where(entry => entry.Content.ContentContainsFolderUrl(type: "Folder"))
                .Select(x => $"{x.Link.Href}/children")
                .ToList();

            var documents = feeds
                .Where(entry => entry.Content.ContentContainsFolderUrl(type: "webi"))
                .ToDictionary(
                    keySelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "id").First().Text,
                    elementSelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "name").First().Text);

            // if there are no more folders or documents to check, break out of this while loop
            if (!documents.Any() && !folders.Any())
                break;

            Console.WriteLine($"Got {folders.Count} folders and {documents.Count} documents");

            folderUrls.RemoveAll(x => true); // remove all the folder urls as they have already been checked!!!!
            folderUrls.AddRange(folders);
            documents.ForEachDo(x => documentUrls.Add(x.Key, x.Value));
        } while (true);

        Console.WriteLine($"There are {documentUrls.Count()} {nameof(documentUrls)}");
...

【问题讨论】:

  • 结束条件是什么?你的 do..while 循环目前是无止境的。在haskell中,是否将其实现为递归调用?但是通过无休止的递归调用,您可能会收到 stackoverflow 异常。
  • 既然它只是一个无限循环,你不妨做一个普通的while循环。
  • 一种更“实用”的方法是使用递归,但在添加错误处理时,你所拥有的很好,并且在许多方面更可取。文件夹的Queue&lt;T&gt; 会比您不断擦除的列表更整洁。
  • @juharr 中断意味着它不是无限的。
  • @TryToSolveItSimple 当没有找到更多文档或文件夹时,其中有一个break

标签: c# functional-programming do-while


【解决方案1】:

做这个更实用的不是你是使用forwhile还是递归。主要是为了避免副作用。

例如您如何使用folderUrls 来跟踪已检查和嵌套的文件夹不是很实用。相反,设计一个函数,它接受一个文件夹列表并返回文档。 IE。定义明确的输入和输出。从您的代码示例中,很难说出您到底想要实现什么。

这是这个函数的样子:

// I don't know what the type of "entry" is. Replace "TEntry" with the correct type.
public IEnumerable<TEntry> LoadDocuments(IEnumerable<TEntry> feedEntries)
{
    var folderUrls = feedEntries
        .Where(entry => entry.Content.ContentContainsFolderUrl(type: "folder"))
        .Select(entry => $"{entry.Link.Href}/children");

    if (!folderUrls.Any())
        return Enumerable.Empty<TEntry>();

    var feeds = folderUrls
        .Select(url => url.GetResponse(headerToken)
                          .GetXmlString()
                          .DeserializeFromXml<Library.Children.Feed>())
        .SelectMany(x => x.Entries)
        .ToList();

    // Recursive call to load nested documents.    
    var nestedDocuments = LoadDocuments(feeds);

    var documents = feeds
        // What's the type of "entry" here? Use that type for the return type of this 
        // function.
        .Where(entry => entry.Content.ContentContainsFolderUrl(type: "webi"));

    return documents.Concat(nestedDocuments);
}

调用它并将这个函数的输出转换成字典:

var rootFeed = $"{baseUrl}/biprws/infostore/{23}/children"
    .Do(str => Console.WriteLine($"{str}:"))
    .GetResponse(headerToken)
    .GetXmlString()
    .DeserializeFromXml<Library.Children.Feed>();

var documentUrls = LoadDocuments(rootFeed.Entries)
    .ToDictionary(
        x => x.Content.Attrs.Attr
              .Where(y => y.Name.ToLower() == "id")
              .First().Text,
        x => x.Content.Attrs.Attr
              .Where(y => y.Name.ToLower() == "name")
              .First().Text);

【讨论】:

  • 我不喜欢的是我的代码中的 folderUrls 变量(你提到的副作用),但除了做之外,我看不到任何其他方式来执行代码...while循环。我刚试过你放的东西,效果很好!
【解决方案2】:

没有无限循环的模式通常会在循环之前为退出条件预加载,

    var folders = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "Folder"))
            .Select(x => $"{x.Link.Href}/children")
            .ToList();

    var documents = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "webi"))
            .ToDictionary(
                keySelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "id").First().Text,
                elementSelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "name").First().Text);

然后使用while循环条件作为退出测试:

while(documents.Any() || folders.Any()){

        var feeds = folderUrls
            .Select(url => url.GetResponse(headerToken).GetXmlString().DeserializeFromXml<Library.Children.Feed>())
            .SelectMany(x => x.Entries);

        Console.WriteLine($"Got {folders.Count} folders and {documents.Count} documents");

        folderUrls.RemoveAll(x => true); // remove all the folder urls as they have already been checked!!!!
        folderUrls.AddRange(folders);
        documents.ForEachDo(x => documentUrls.Add(x.Key, x.Value));

最后在再次循环之前加载退出条件的变量:

        folders = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "Folder"))
            .Select(x => $"{x.Link.Href}/children")
            .ToList();

        documents = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "webi"))
            .ToDictionary(
                keySelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "id").First().Text,
                elementSelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "name").First().Text);

}

...

这种方法会重复代码,因此为每个查询创建方法,因此重复的代码只是对适当方法的几次调用。这将导致代码更具可读性,因为描述性方法名称在代码中比 LINQ 查询更容易遵循(更不用说它不会有无限循环)。这种模式可能需要更多的努力来处理变量范围,但在这种情况下,这不应该是一个问题(feed 应该是这种情况下方法的唯一参数)。

【讨论】:

    【解决方案3】:

    自己关心打字

    static void Main(string[] args)
    {
        var documentUrls = new Dictionary<string, string>();
    
        Feed rootFeed = $"{baseUrl}/biprws/infostore/{23}/children"
            .Do(str => Console.WriteLine($"{str}:"))
            .GetResponse(headerToken)
            .GetXmlString()
            .DeserializeFromXml<Library.Children.Feed>();
    
        IEnumerable<object> folderUrls = rootFeed.Entries
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "folder"))
            .Select(entry => $"{entry.Link.Href}/children")
            .ToList();
    
        object feeds;
        object folders;
        object documents;
    
        Init(folderUrls, out feeds, out folders, out documents);
    
        while (documents.Any() || folders.Any())
        {
            Console.WriteLine($"Got {folders.Count} folders and {documents.Count} documents");
    
            folderUrls.RemoveAll(x => true);
            folderUrls.AddRange(folders);
            documents.ForEachDo(x => documentUrls.Add(x.Key, x.Value));
    
            Init(folderUrls, out feeds, out folders, out documents);
        }
    
        Console.WriteLine($"There are {documentUrls.Count()} {nameof(documentUrls)}");
    
    }
    
    private static void Init(object foldersUrl, out object feeds, out object folders, out object documents)
    {
        feeds = InitFeeds(foldersUrl);
        folders = InitFolders(feeds);
        documents = InitDocs(feeds);
    }
    
    private static object InitDocs(object feeds)
    {
        var documents = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "webi"))
            .ToDictionary(
                keySelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "id").First().Text,
                elementSelector: x => x.Content.Attrs.Attr.Where(y => y.Name.ToLower() == "name").First().Text);
        return documents;
    }
    
    private static object InitFolders(object feeds)
    {
        var folders = feeds
            .Where(entry => entry.Content.ContentContainsFolderUrl(type: "Folder"))
            .Select(x => $"{x.Link.Href}/children")
            .ToList();
        return folders;
    }
    
    private static object InitFeeds(object folderUrls)
    {
        var feeds = folderUrls
            .Select(url => url.GetResponse(headerToken).GetXmlString().DeserializeFromXml<Feed>())
            .SelectMany(x => x.Entries);
        return feeds;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-11-16
      • 1970-01-01
      • 2014-01-11
      • 2018-05-09
      • 2012-12-24
      • 2016-09-23
      • 2016-08-05
      • 2014-10-03
      • 1970-01-01
      相关资源
      最近更新 更多