C#lambda表达式是否会返回void?
我有以下方法, 我想知道下面是否有任何东西可以使用default(void),因为有一个编译器错误,说明void在这里无效:
private void applyDefaultsIfNecessary(ApplicationConfiguration configuration) { var defaults = new Dictionary<Predicate, Action>() { // { 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((item) => { if (item.Key.Invoke(configuration)) item.Value.Invoke(configuration); } );
我从语言设计的角度来看很奇怪,为什么我们没有可以返回void的表达式或者无效的变量的数据类型。
我将引用您的规范7.1节,其中规定:
[表达式可以被分类为“无”)。 当表达式是返回类型为void的方法的调用时,会发生这种情况。 分类为空的表达式仅在语句表达式的上下文中有效。
[强调补充]。
也就是说, 只有当表达式构成一个完整的语句时,才能使用一个表达式,这是一个返回void的方法 。 像这样:
M();
实际上,这违反了函数式编程规则。 这与Eric Lippert描述的List.ForEach有同样的缺陷:你正在试图对你的collections造成副作用。
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);
从语言的角度来看, void
意味着“不存在”,这就引出了一个问题:声明一个不存在的变量会有什么价值?
这里的问题不是语言限制,而是你使用一个需要两个rvalues的构造(三元运算符)的事实 – 你只有一个rvalues。
如果我可能会直言不讳,我会说你会避免if / else赞成毫无意义的简洁。 你提出来替换default(void)
任何技巧只会在你不再烦恼这种事情之后很久就会混淆其他开发人员或你。
首先,你应该避免将副作用放在标准的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();
但我真的不能推荐这样做。
默认不适用于void; 但它适用于某种类型。 Action类不产生任何结果,但Func <>对象总是必须返回结果。 无论item.Value.Invoke()返回只返回默认值,如:
默认(对象)
或者如果是特定类型:
默认(SOMETYPE)
像那样。