【问题标题】:C#8: Switch expression return tupleC#8:开关表达式返回元组
【发布时间】:2023-02-09 00:10:41
【问题描述】:

为什么这个

(Func<Window> win1, int i1) = ( () => new Window(), 1);

和这个

(int i2, int i3) = 1 switch { 
   1 => (2, 1),
   _ => throw new ArgumentOutOfRangeException()
};

作品。

但事实并非如此

(Func<Window> win2, int i4) = 1 switch {
   1 => (() => new Window(), 1),
   _ => throw new ArgumentOutOfRangeException()
};

【问题讨论】:

  • 在没有元组解构的情况下工作(Func&lt;Window&gt; win2, int i4) tmpvar = 1 switch { 1 =&gt; (() =&gt; new Window(), 1), _ =&gt; throw new ArgumentOutOfRangeException() };

标签: tuples switch-statement expression c#-8.0


【解决方案1】:

这些不一致背后的原因是因为您的不同示例涉及解构器和类型推断的不同方面。

您的第一个案例是添加到 C# 7.1 的优化的副作用示例,它优化了元组的解构。这是为了允许使用构造函数模式而引入的:

public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age) => (Name, Age) = (name, age);
}

而不会产生分配元组然后解构它的开销。编译器将该构造函数“降低”为:

public Person(string name, int age)
{
    Name = name;
    Age = age;
}

因此,当编译器遇到:

(Func<Window> win1, int i1) = ( () => new Window(), 1);

它将其转换为:

Func<Window> win1 = new Window();
int i1 1;

所有类型都是已知的,所以一切都很好。

如果我们将其更改为:您的下一个示例可以得到更好的解释:

var a = 1 switch { 
   1 => (2, 1),
   _ => throw new ArgumentOutOfRangeException()
};

该表达式利用了编译器在确定 switch 表达式的类型时忽略 throw 的事实。只有另外一只手臂,它可以推断其类型为(int, int)。它选择它作为 switch 表达式的返回类型,a 被推断为 (int, int) 类型。

在您的示例中,您将获取该元组并将其解构为两个 int:i1i2。因为表达式的类型是已知的,所以编译器很高兴它知道如何解构该表达式。

在解决您的最后一个示例之前,我们再次需要对其进行稍微修改:

(Func<Window>, int) tmp = 1 switch {
   1 => (() => new Window(), 1),
   _ => throw new ArgumentOutOfRangeException()
};

此代码编译。原因是因为我们现在使用的是目标类型的 switch 表达式。如果编译器无法从该表达式的分支中推断出 switch 表达式的类型,那么它会查看表达式的目标(在本例中为赋值)以查看其是否具有满足 switch 表达式的所有分支的类型. C# 不会为 lambda 推断委托类型;它要求它们被显式键入或推断。在此示例中,(Func&lt;Window&gt;, int) 类型适用于 switch 表达式的所有臂,因此使用该类型。

尽管您对作业的 lhs 进行了解构,但这种方法不适用于您的示例。解构需要知道它正在解构的类型,而 switch 表达式需要一个目标类型。正如您在评论中指出的那样,编译器有一个怪癖可以同时满足这两个条件:在解构之后删除一个变量:

(Func<Window> win2, int i4) tmp = 1 switch {
   1 => (() => new Window(), 1),
   _ => throw new ArgumentOutOfRangeException()
};

然后编译器将其降低为:

(Func<Window>, int) tmp = 1 switch {
   1 => (() => new Window(), 1),
   _ => throw new ArgumentOutOfRangeException()
};
(Func<Window> win2, int i4) = tmp;

编译器当然可以避免这种情况并自行推断该变量,但这需要作为对语言的增强而提出和采用。如果您想这样做,请前往dotnet/csharplang discussions on github

更新:我在那里创建了一个讨论:Should C# support mixing deconstructors and target-typed switch/option expressions,因为这似乎是一个重要的讨论点。

【讨论】:

    猜你喜欢
    • 2020-12-16
    • 2020-09-13
    • 2011-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多