C# 声明具有表达式类型的变量

C# declaring a variable with the type of an expression

我看到有一些关于此主题的旧问题 (2011),只是想知道是否有任何我可能遗漏的更改。主题领域是自动类型推导,特别是在声明变量时。这里的目标是通过避免代码中的重复来减少输入,从而减少错误。理想情况下,解决方案应该具有零运行时开销,并且阅读代码的人很容易理解。

我有一个功能

(tuple-type-decl) Function(param-list) { body }

我想声明一个变量

var list = new List<return-type-of-function>();

[已添加]一个具体的例子

var list = new list<typeof Function(default,fault)>();

也就是说,我希望编译器向 List 提供函数返回的类型的类型参数,而不需要我重新键入或显式命名该类型。我在这里寻找成语,而不是如何解决特定用例。

当然,我不能直接在 C# 中这样做。我认为现代 C++ 实际上可以让我基本上编写该声明,但 C# typeof 运算符仅适用于类型,不适用于表达式。我能想出的最好的(它远非“好”)是:

var list = (from i in Enumerable.Range(0,0) select Function(bogus-parms).ToList();

这实际上确实声明了一个正确类型的列表,甚至从元组类型中提取了伪成员名称,但它很丑陋,而且远非清晰,而且实际上有一个运行时成本,尽管很小。

有没有人在 C# 7/8 时代为这个构造想出一个像样的习惯用法?

您可以为 T

创建扩展方法
public static List<T> ToList<T>(this T t) => new List<T>();

https://dotnetfiddle.net/KKCDZO

加长版: https://dotnetfiddle.net/MDunr1

假设您的函数声明为

(string name, int x, int y) Function(param-list) { body }

然后你可以声明一个列表为

List<(string name, int x, int y)> list =
    (from i in Enumerable.Range(0,0) select Function(bogus-parms).ToList();

请注意,我使用的是新的 ValueTuple 类型。它从 C# 7.0 开始就存在,并允许您为元组字段命名。

然后您可以像这样访问项目

string name = list[i].name;
int x = list[i].x;
int y = list[i].y;

var (name, x, y) = list[i];
Console.WriteLine($"name = {name}, x = {x}, y = {y}");

这也是新的,叫做String interpolation

当然你可以创建自己的类型

public readonly struct MyStruct
{
    public MyStruct(string name, int x, int y)
    {
        Name = name;
        X = x;
        Y = y;
    }

    public string Name { get; }
    public int X { get; }
    public int Y { get; }
}

现在类型有名字了,它的成员也有名字了。您可以将列表声明为

List<MyStruct> list = ...;

它也适用于

var list = ...;

如果右边的表达式返回 List<MyStruct>,即如果函数的 return 类型为 MyStruct。请注意,第二种情况中的列表被强类型化为 List<MyStruct>。您无需明确声明它,而只是让 C# 编译器推断它。这发生在编译时,对运行时没有影响。

您可以创建一个新的 class,它从您选择的 Tuple<> 扩展而来。例如,您可以声明一个 class,例如:

public class MyThing : Tuple<int, string> {
    public MyThing(int a, string b) : base(a, b) {}
}

然后你就可以像其他类型一样在任何地方使用这个新的 class MyThing

public static void Main(string[] args) {
    var obj = Foobar("test");
    Console.WriteLine(obj);
    Console.WriteLine(obj.Item1);
    Console.WriteLine(obj.Item2);

    IList<MyThing> list = new List<MyThing> {
        obj
    };
    Console.WriteLine(list);
}

public static MyThing Foobar(string b) {
    return new MyThing(b.Length, b);
}

这将生成以下输出:

(4, test)
4
test
System.Collections.Generic.List`1[MyThing]

您可以使用接受 Expression<Func<T>> 然后 returns 和 List<T> 的辅助函数来实现,但您仍然必须指定 "bogus" 参数

public static List<T> ListFor<T>(Expression<Func<T>> unused)
    => new List<T>();

然后您可以像下面这样使用它:

public static (int, string, double) MyFunc(int arg1, DateTime arg2)
{ 
    ... 
} 

var list = ListFor(() => MyFunc(default, default));

您甚至可以创建匿名类型的列表(虽然我不确定您为什么要这样做):

var list = ListFor(() => new { s = "", i = 5 });