匿名类型的非只读替代方案

时间:2012-01-28 08:07:48

标签: c# data-structures struct anonymous-types

在C#中,匿名类型可以如下:

method doStuff(){
     var myVar = new {
         a = false, 
         b = true
     }

     if (myVar.a) 
     {
         // Do stuff             
     }
}

但是,以下内容无法编译:

method doStuff(){
     var myVar = new {
         a = false, 
         b = true
     }

     if (myVar.a) 
     {
         myVar.b = true;
     }
}

这是因为myVar的字段是只读的,无法分配。似乎想做一些像后者相当普遍的事情;也许我见过的最好的解决方案就是在方法之外定义一个结构。

然而,真的没有办法让上面的块工作吗?困扰我的原因是,myVar是这个字段的局部变量,所以它似乎只应该在使用它的方法中引用。此外,需要将struct放在方法之外可以使对象的声明远离其使用,特别是在长方法中。

换句话说,有没有匿名类型的替代方法,这将允许我定义这样的“结构”(我意识到结构存在于C#中并且必须在方法之外定义)而不使其成为只读?如果不是,想要做到这一点是否存在根本性的错误,我应该采用不同的方法吗?

6 个答案:

答案 0 :(得分:30)

不,你必须创建自己的类或结构来做到这一点(如果你希望它是可变的,最好是一个类 - 可变结构很可怕)。

如果您不关心Equals / ToString / GetHashCode实施,那很容易:

public class MyClass {
    public bool Foo { get; set; }
    public bool Bar { get; set; }
}

(我仍然使用属性而不是字段,for various reasons。)

我个人通常发现自己想要一个 immutable 类型,我可以在方法之间传递等等 - 我想要一个现有匿名类型功能的命名版本......

答案 1 :(得分:24)

  

是否有匿名类型的替代方法,这将允许我简明地定义这样一个简单的“记录”类型而不使其成为只读?

没有。你必须做出名义上的类型。

  

如果不是,想要这样做是否存在根本性的错误?

不,这是我们之前考虑的合理功能。

我注意到在Visual Basic中,如果你想要它们,匿名类型可变的。

关于可变匿名类型唯一真正“根本错误”的是将一个用作哈希键是危险的。我们设计了匿名类型,其假设是:(1)您将在LINQ查询理解中将它们用作等值连接中的键,以及(2)在LINQ到对象和其他实现中,将使用哈希表实现连接。因此,匿名类型应该用作散列键,并且可变散列键是危险的。

在Visual Basic中,GetHashCode实现不使用匿名类型的可变字段中的任何信息。虽然这是一个合理的妥协,但我们只是认为在C#中额外的复杂性并不值得付出努力。

答案 2 :(得分:3)

您无法获得良好的初始化语法,但.NET 4中引入的ExpandoObject类将成为可行的解决方案。

dynamic eo = new ExpandoObject();

eo.SomeIntValue = 5;
eo.SomeIntValue = 10; // works fine

答案 3 :(得分:2)

对于上述类型的操作,您应该定义自己的可变 STRUCT 。可变结构可能会让像Eric Lippert这样的编译器编写者头疼,并且.net处理它们有一些不幸的限制,但仍然是可变“普通旧数据”结构的语义(所有字段都是公共的结构,唯一的结构)编写this的公共函数是构造函数,或者只是从构造函数中调用的函数提供了比通过类实现的更清晰的语义。

例如,请考虑以下事项:

struct Foo {
  public int bar; 
  ...other stuff;
}
int test(Action<Foo[]> proc1, Action<Foo> proc2)
{
  foo myFoos[] = new Foo[100];
  proc1(myFoos);
  myFoos[4].bar = 9;
  proc2(myFoos[4]); // Pass-by-value
  return myFoos[4].bar;
}

假设没有不安全的代码,并且可以调用传入的代理并在有限的时间内返回,test()会返回什么? Foo是具有公共字段bar的结构的事实足以回答这个问题:它将返回9,无论Foo的声明中出现什么,并且无论在proc1proc2中传递了哪些函数。如果Foo是一个类,则必须检查存在或将要存在的每个Action<Foo[]>Action<Foo>,以了解test()将返回什么。确定Foo是具有公共字段bar的结构似乎比检查可能传入的所有过去和将来的函数要容易得多。

修改this的结构方法在.net中的处理特别差,所以如果需要使用方法来修改结构,那么使用这些模式之一几乎肯定更好:

  myStruct = myStruct.ModifiedInSomeFashion(...);  // Approach #1
  myStructType.ModifyInSomeFashion(ref myStruct, ...);  // Approach #2

比模式:

  myStruct.ModifyInSomeFashion(...);

如果使用上述方法对结构修改模式进行修改,则可变结构的优点是允许代码比不可变结构或不可变类更高效,更易于阅读,并且比可变类更不容易出错。类。对于表示值聚合的事物,在它们包含的值之外没有任何标识,可变类类型通常是最糟糕的表示形式。

答案 4 :(得分:2)

在C#7中,我们可以利用named tuples来解决这个问题:

(bool a, bool b) myVar = (false, true);

if (myVar.a)
{
    myVar.b = true;
}

答案 5 :(得分:0)

我感到非常烦人的是,您无法像在VB中一样将匿名属性设置为可读/写-通常我想使用EF / LINQ投影从数据库返回数据,然后对其中的数据进行一些处理由于某种原因在数据库上无法执行的C#。最简单的方法是遍历现有的匿名实例并随时更新属性。注意,现在在EF.Core中这还不错,因为您最终可以在单个查询中混合使用db函数和.net函数。

我的首选解决方法是使用反射,将被皱眉和否决,但是可行;买方要当心基本实现是否发生变化以及您的所有代码中断。

public static class AnonClassHelper {

    public static void SetField<T>(object anonClass, string fieldName, T value) {
        var field = anonClass.GetType().GetField($"<{fieldName}>i__Field", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        field.SetValue(anonClass, value);
    }

}
// usage
AnonClassHelper.SetField(inst, nameof(inst.SomeField), newVal);

在处理字符串时,我使用的另一种方法是设置StringBuilder类型的属性,然后在您拥有匿名类型的实例之后,可以通过StringBuilder方法设置这些单独的属性。