16

我有以下方法,我想知道下面是否有任何可以放在 default(void) 的地方,因为有一个编译器错误表明 void 在这里无效:

private void applyDefaultsIfNecessary(ApplicationConfiguration configuration)
{
    var defaults = new Dictionary<Predicate<ApplicationConfiguration>, Action<ApplicationConfiguration>>()
    {
       // { rule, action } - if rule is true, execute action 
       { (c) => c.ConnectionString == null , (c) => c.ConnectionString = "foo" },
       { (c) => c.OutputExcelFilePath == null, (c) => c.ConnectionString = "bar" },
       { (c) => c.OutputDirectory == null, (c) => c.OutputDirectory = "baz" }

    };

    //Nothing to select, but we want to loop throough the dict and invoke action, if rule is true.
    //It is a pity there is no extension method called DoForEach on collections.
    defaults.Select((item) => item.Key.Invoke(configuration) ? item.Value.Invoke(configuration) : default(void)  );
}

我意识到我可以使用 if-else 语句而不是三元运算符(或者我可以调用一个虚拟方法来返回 void)。此外,Select 扩展方法不喜欢返回 void 的 lambda。似乎说无法推断类型,但当然如果我这样指定类型,要么:

defaults.Select<ApplicationConfiguration, void>((item) => { if (item.Key.Invoke(configuration)) item.Value.Invoke(configuration); } );

从语言设计的角度来看,我很好奇,为什么我们没有可以返回 void 的表达式或 void 变量的数据类型。

4

5 回答 5

14

我请您参考规范的第 7.1 节,其中指出:

[一个表达式可以归类为]“无”。当表达式调用返回类型为 void 的方法时,就会发生这种情况。 分类为无的表达式仅在语句表达式的上下文中有效。

[强调补充]。

也就是说,您唯一可以使用返回 void 方法调用的表达式是当表达式构成整个语句时。像这样:

M();
于 2010-01-08T19:29:15.650 回答
11

这实际上违反了函数式编程规则。这与Eric Lippert 描述的关于 List.ForEach的缺陷相同:您在哲学上试图对您的收藏造成副作用。

Enumerable.Select 旨在返回一个新集合 - 过滤输入。它不打算执行代码。

话虽如此,您可以通过以下方式解决此问题:

defaults.Where(item => item.Key.Invoke(configuration)).ToList().ForEach( item => item.Value.Invoke(configuration));

它只是不像这样做那么清楚:

var matches = defaults.Where(item => item.Key.Invoke(configuration));
foreach(var match in matches)
    match.Value.Invoke(configuration);
于 2010-01-08T17:48:14.937 回答
6

首先,您应该真正避免将副作用放在标准 linq 查询运算符中,其次这实际上不起作用,因为您没有在任何地方枚举 Select 查询。如果你想使用 linq,你可以这样做:

foreach(var item in defaults.Where(i => i.Key.Invoke(configuration)))
{
   item.Value.Invoke(configuration);   
}

关于你的问题,我很确定没有可能的 void 值,你不能明确地返回它。在 F# 等函数式语言中,void 被替换为“unit”,即只有一个可能值的类型 - 如果您愿意,您可以创建自己的单位类型并返回它。在这种情况下,您可以执行以下操作:

defaults.Select(item => {
    if(item.Key.Invoke(configuration))
    {
        item.Value.Invoke(configuration);
    }
    return Unit.Value;
}).ToList();

但我真的不建议这样做。

于 2010-01-08T17:49:32.077 回答
3

从语言的角度来看,void意思是“不存在”,这就引出了一个问题:声明一个不存在的变量会有什么价值?

这里的问题不是语言限制,而是您使用的构造(三元运算符)需要两个右值 - 您只有一个。

如果我直言不讳,我会说你是在避免 if/else 以支持毫无意义的简洁。您想出的任何替换技巧default(void)只会使其他开发人员感到困惑,或者将来,在您不再为此类事情烦恼很久之后。

于 2010-01-08T17:46:30.523 回答
2

默认值不适用于 void;但它适用于一种类型。Action 类不产生任何结果,但 Func<> 对象总是必须返回结果。无论 item.Value.Invoke() 返回什么,都只返回默认值,如下所示:

默认(对象)

或者如果它是特定类型:

默认(某些类型)

像那样。

于 2010-01-08T17:39:47.930 回答