如何在不创建实例的情况下声明C#匿名类型?

13

有没有更好的方法可以声明一个匿名类型,而不需要创建它的实例?

var hashSet = new [] { new { Name = (string)null } }.Take(0).ToHashSet(); // HashSet<T>
using (new Scope())
{
    hashSet.Add(new { Name = "Boaty" });
    hashSet.Add(new { Name = "McBoatface" });
}
using (new AnotherScope())
{
    return names.Where(x => hashSet.Contains(new { x.Name }));
}

我不喜欢上面第一行采用的笨拙方法,但它确实使我可以在后来的不同作用域中使用HashSet。

编辑: 第二个例子稍微更全面:

private IEnumerable<Person> _people;

public IEnumerable<Person> People()
{
    HashSet<T> hashSet;
    using (var new Scope())
    {
        // load a filter from somewhere else (oversimplified here to a single literal objects of an anonymous type)
        hashSet = new []
        {
            new { FirstName = "Boaty", LastName = "McBoatface" },
        }.ToHashSet();
    }
    using (var new AnotherScope())
    {
         return _people.Where(x => hashSet.Contains(new { FirstName = x.Nombre, LastName = x.Apellido }));
    }
}

请注意,对于这个特定的例子,您可以使用字符串而不是匿名类型。 - TheLethalCoder
@Jono 虽然这个需求很奇怪,但你解决方案中所描述的方法似乎是唯一的。 - MakePeaceGreatAgain
1
如果您需要创建多个HashSet实例,可以通过将空的可枚举对象存储在变量中来简化操作。然后,您可以调用variable.ToHashSet()方法。或者您可以编写一个函数来实现。但是,创建匿名类型的唯一方法是创建其实例。 - Dennis_E
你在编辑器中写的代码完全乱了。 - Dennis_E
@Jono 不好意思,我说的“messed up”是指错误的意思。一开始using (var new Scope())就不是有效的。现在你写的是new [] { hashSet.Add(new {...etc,你的意思应该是new [] { new { ...etc - Dennis_E
显示剩余8条评论
2个回答

12

实际上没有办法做到这一点,匿名对象总是有一些对象初始化(使用 new)。

匿名类型是一种“设置和忘记”的类型,这意味着它们只在一个短代码片段中使用一次,例如LINQ表达式,然后忘记它们的存在。

但是,您应该问自己为什么需要这个。当您需要在类中传递列表时,请为其实体命名。通过在不同的作用域中使用相同的匿名类型,您能获得什么?清晰明了地表达出您的意图,以便每个开发人员都能理解列表包含的内容以及他/她可以从中接受什么。

因此,最好使用(私有)struct,您可以在其中使用它。

class CyClass
{
    private struct Person { public string Name; }
    
    HashSet<Person> hashSet = new HashSet<Person>();

    ...

        using (var firstScope = new Scope())
        {
            hashSet.Add(new Person { Name = "Boaty" });
            hashSet.Add(new Person { Name = "McBoatface" });
        }
    
        using (var secondScope = new AnotherScope())
        {
            return names.Where(x => hashSet.Contains(new Person{ x.Name }));
        }
}

MSDN明确表示:

如果您必须存储查询结果或将其传递到方法边界之外,请考虑使用普通命名的结构体或类,而不是匿名类型

然而,我不会仅限于如第二段所述的方法边界。

编辑:要回答您的问题,是否可能创建一个未实例化的匿名类型,请参阅MSDN中的以下语句:

通过使用new运算符和对象初始化程序,您可以创建匿名类型

编辑2:从C#7开始,您可以在列表中使用元组。 但是,元组至少有两个属性,因此您的第一个示例在此处无法使用:

var myList = new List<(string FirstName, string LastName)>();
myList.Add(("Boaty", "McBoatface"));
现在你可以检查你的另一个列表是否包含这样的元组:
var contained = anotherList.Contains(("Boaty", "McBoatface"));

你的回答意味着我不应该使用匿名类型,而是应该声明自己的命名类型。我的代码示例确实非常简单,但是这种类型在方法之间并不共享;实际上,它只在单个方法中使用了几次,并且所有用法都在彼此相距不到5行的地方。我更喜欢使用匿名类型。例如,它使第一个作用域能够从Entity Framework填充HashSet。 - Jono
请看我的第二段。 - MakePeaceGreatAgain
我只使用它一次。我在一个作用域中填充值,然后在第二个作用域中使用它们。这是在单个方法内完成的(请参见我的第一个评论)。我认为这是匿名类型的正确用法,尽管我意识到我想要从最常见的情况稍微弯曲规则。 - Jono
@Marcel,我不明白你的意思。你所说的“the”匿名类型是什么意思? - MakePeaceGreatAgain
我认为关于匿名类型的答案是错误的。可以创建它们的初始变量。请参阅我的其他答案。 尽管如此,我同意在可能的情况下使用ValueTuples的建议。不幸的是,一些LINQ提供程序(例如ORM)不支持它们。 - Marcel

2
您可以在不创建该类型的实例的情况下创建匿名类型的变量。
public static T DefaultReturnValue<T>(this Action<T> _) => default;

var initialVariable = DefaultReturnValue(() => new { Name = "x" });

Lambda中的操作从未执行。 DefaultReturnValue 只是提取返回类型。
您可以稍后将值分配给此变量。
initialVariable = new { Name = "y" };

使用这个方法作为 OP 的第二个示例,初始的 HashSet 变量可以被声明,而不必创建一个多余的实例

var hashSet = DefaultReturnValue(() => new [] { new { Name = (string)null } }.ToHashSet());

1
我不认为这在代码上有什么不同,与OP最初的提议相比。您仍然需要在代码中定义一个“虚拟”实例;它从未被创建的事实在我看来并不相关。 - D Stanley
@DStanley 我认为这不仅与主题无关,而且相当丑陋。 - MakePeaceGreatAgain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接