【问题标题】:Non-read only alternative to anonymous types匿名类型的非只读替代方案
【发布时间】:2022-03-10 06:55:50
【问题描述】:

在 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 是该字段的局部变量,因此似乎只能在使用它的方法内部引用它。此外,需要将结构体放在方法之外会使对象的声明与其用途相去甚远,尤其是在长方法中。

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

【问题讨论】:

  • 匿名类型被设计为一次性,快速使用。如果你的使用比这更复杂,有所有的 readsons 来创建一个命名类型。

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


【解决方案1】:

不,您必须创建自己的类或结构来执行此操作(如果您希望它是可变的,最好是一个类 - 可变结构是可怕的)。

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

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

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

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

【讨论】:

  • 我发现自己想要的是声明具有特定字段的结构的快速简写,以及将这些字段作为参数的自动生成的构造函数。请注意,顺便说一句,如果一个结构没有任何修改this 的成员函数或访问外部实体的属性,一个“不可变”结构和一个“可变”结构将无法与任何不区分的代码' t 直接尝试修改其字段。如果一个人不知道自己在做什么,我可以看到反对尝试修改结构字段的论据,但这不是让结构禁止这种修改的论据。
  • @supercat:我已经看到了足够多的由可变结构引起的问题,除了最专业的情况外,我可以避免它们。这种情况确实存在,但我发现它们很少见。鉴于您的回答,我怀疑我们必须同意不同意可变结构是否是一个好主意。
  • 这些问题中有多少不涉及写入this 的结构方法?你能指出我所看到的任何(非本文写作)问题的任何描述,这些问题不是不了解结构如何工作的人的明确结果(例如,从集合中获取某些东西,修改那个东西,以及愉快地期待它更新集合——这可能适用于类,也可能不适用于类,当然不适用于结构)?
  • @supercat - 虽然您可能知道可变结构的所有可能陷阱,但您能保证每个维护或(如果公开)使用您的代码的开发人员都会意识到这些吗?虽然我不相信编码到最低公分母,但可变结构与我喜欢的“成功坑”相反。
  • @supercat:请留意 Stack Overflow 上的内容。这不仅仅是当事情编译然后没有按预期工作时 - 人们期望能够在实际上对他们没有帮助的情况下修改事情......
【解决方案2】:

是否有匿名类型的替代方法可以让我简洁地定义一个像这样的简单“记录”类型而不使其成为只读类型?

没有。您必须创建一个名义类型。

如果没有,想要这样做有什么根本错误吗?

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

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

关于可变匿名类型,唯一真正“根本错误”的事情是使用它作为哈希键是危险的。我们设计匿名类型的假设是 (1) 您将在 LINQ 查询理解中将它们用作等值连接中的键,以及 (2) 在 LINQ-to-Objects 和其他实现中,连接将使用哈希表来实现。因此匿名类型作为散列键应该是有用的,而可变散列键是危险的。

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

【讨论】:

  • 我没有意识到 vb.net 使用具有可变属性的类来实现匿名类型。这似乎是最糟糕的选择。它失去了从暴露字段(只读或非只读)中获得的所有性能优势,从可变结构中获得的所有语义优势(通过任何没有足够访问权限来替换实例的代码来防止突变)一个新的)或不可变类型。
  • @supercat:它们在 VB 中是可选地可变的。如果您不喜欢该功能,则不必使用它。
  • 啊,我明白了。声明为“键”的属性是不可变的——只有那些不可变的。我仍然不确定通过使用可变属性而不是字段来获得什么。如果类或(可能未来的)派生可能需要非默认行为,则成员需要是属性而不是字段,但由于匿名类的可变成员将始终使用默认行为,我不清楚通过制作获得了什么它们的属性。
  • @supercat:使它们成为属性的一个合理原因是只有属性可以在 WPF 中绑定。因此,使用属性使匿名类可用作 WPF 的数据源。虽然这可能不是语言设计者/实施者做出决定的原因,但它确实支持上述决定。
  • @KevinCathcart:我没用过 WPF;是它使用反射来尝试从附加的类实例中获取属性的想法吗? WPF 对可变性有什么期望?匿名类型当然没有必要的基础设施来支持异步访问上下文中的安全变异。
【解决方案3】:

在 C# 7 中,我们可以利用 named tuples 来达到目的:

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

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

【讨论】:

  • :(我希望我可以在 C# 6 中使用这个功能,我想我目前的项目不走运......(除非我能说服一些人升级)
【解决方案4】:

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

dynamic eo = new ExpandoObject();

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

【讨论】:

  • 我不认为ExpandoObject 支持命名的、类型化的、空值属性(因为它只是一个字典,所有null 值都将是非类型化的),而匿名类型支持。
【解决方案5】:

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

例如,考虑以下情况:

结构富{ 公共 int 栏; ...其他的东西; } int test(Action proc1, Action proc2) { foo myFoos[] = new Foo[100]; proc1(myFoos); myFoos[4].bar = 9; proc2(myFoos[4]); // 值传递 返回 myFoos[4].bar; }

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

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

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

比模式:

myStruct.ModifyInSomeFashion(...);

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

【讨论】:

  • “更清晰的语义”显然是旁观者,但我不同意所有公共成员的结构将满足许多初学者的期望。按值传递的可变结构的语义有许多微妙的陷阱,如本响应中推荐的钝角修改模式所示。公共字段也打破了结构的封装,这是 OOP 的主要优点之一。与 Eric 和其他语言设计师一起做这个——只是不要这样做。
  • @JohnMelville:具有公共字段的结构的行为本质上就像一堆用胶带粘在一起的存储位置。如果想用胶带将一堆存储位置粘在一起,使用实际使用 BoSLSTwDT(结构)的代码将比尝试使用其他类型来模拟它们的代码更清晰。使结构混乱的主要因素是语言努力假装它们是Object 的一种形式。
【解决方案6】:

我发现你不能像在 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 方法设置。

【讨论】:

    【解决方案7】:

    我知道这确实是个老问题,但是如何替换整个匿名 目的: `

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

    `

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-02
      • 1970-01-01
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多