【问题标题】:How to store all ctor parameters in fields如何将所有ctor参数存储在字段中
【发布时间】:2019-07-16 09:53:36
【问题描述】:

我正在学习 C#,并且在编码时出现了一个想法。是否可以通过简单的方式将构造函数中的参数自动存储到字段中,而无需在每个变量上写this.var = var 来存储它们?

例子:

class MyClass
{
    int var1;
    int var2;
    int var3;
    int var4;
    public MyClass(int var1, int var2, int var3, int var4){
        this.var1 = var1;
        this.var2 = var2;
        this.var3 = var3;
        this.var4 = var4;
    }
}

有没有办法避免写入this.varX = varX,如果名称相同,则将所有变量保存到字段中?

【问题讨论】:

  • 据我所知没有! :(
  • 没有。但是字段和参数应该具有相似但不同的名称 - 例如,字段始终以下划线开头是一种相当常见的将它们与其他任何东西区分开来的方法。您仍然需要在构造函数中显式设置字段,但您不必为此使用 this 关键字:(即 _var1 = var1
  • 不,只是为了加快速度,您可以这样做(在 VS 中):docs.microsoft.com/en-us/visualstudio/ide/reference/…
  • 写入参数(例如int var1)。选择var1 并按Control .。将出现选项为您编写分配代码。
  • 另外,我不认为oop 标签是正确的标签。

标签: c# oop


【解决方案1】:

如果你先定义你的变量,你可以使用visual studios的“快速操作”工具为你生成一个构造函数;这使您可以选择要包含的当前定义的类字段。

使用它会插入一个构造函数类,其中包含您选择的所有字段作为参数,并将值分配给字段。

这不会减少代码量,但会减少您需要的打字量

【讨论】:

    【解决方案2】:

    不,在当前版本的 C# 中没有更容易做到这一点的方法。 C# 6.0 预发行版中有一个名为 Primary Constructors 的新功能可以解决此问题,但在最终版本之前已将其删除。 https://www.c-sharpcorner.com/UploadFile/7ca517/primary-constructor-is-removed-from-C-Sharp-6-0/

    目前,我相信 C# 团队正在努力将记录添加到语言中:https://github.com/dotnet/roslyn/blob/features/records/docs/features/records.md - 这应该会使处理简单数据类变得更加简单,就像在 F# 中一样

    【讨论】:

    • 你知道为什么主构造函数被移除了吗?
    • 我认为公告已经丢失,因为它们已发布在现已失效的 Roslyn codeplex 页面上,但我认为该功能存在争议,因为对于主构造函数体内应允许的内容存在不同意见。后来决定只在记录上允许主构造函数是一个更好的解决方案,因为它留下了更少的歧义空间。
    【解决方案3】:

    短:不,长:是的,有一个 hack。

    您可以混合使用反射并将参数存储在临时数组中来实现这一点。

    class TestClass
    {
        public string var1 { get; set; }
        public string var2 { get; set; }
        public string var3 { get; set; }
    
        public TestClass(string var1, string var2, string var3) : base()
        {
            var param = new { var1, var2, var3 };
            PropertyInfo[] info = this.GetType().GetProperties();
    
            foreach (PropertyInfo infos in info) {
                foreach (PropertyInfo paramInfo in param.GetType().GetProperties()) {
                    if (infos.Name == paramInfo.Name) {
                        infos.SetValue(this, paramInfo.GetValue(param, null));
                    }
                }
            }
    
        }
    
    }
    
    

    这基本上循环遍历属性并检查名称是否等于参数名称,这些名称已存储在临时数组中(您无法通过反射获取参数值),如果它们匹配,则分配它。

    注意:我不建议分配这样的属性,但为了证明我可能想出了这个。

    【讨论】:

    • 聪明的把戏,虽然我认为值得注意的是,从运行时成本的角度来看,反射是昂贵的。就个人而言,任何时候我想要这个,我都在做一个我真的不想支付这些费用的课程。话虽如此,它确实可以完成这项工作!
    【解决方案4】:

    您的问题的答案是否定的,但您可以使用属性来获得类似的修复:

    class MyClass
    {
        public int Var1 {get;set;}
        public int Var2 {get;set;}
        public int Var3 {get;set;}
        public int Var4 {get;set;}
        public MyClass(){
    
        }
    }
    
    void Main()
    {
       var myClass = new MyClass
       {
         Var1 = 1,
         Var2 = 2,
         Var3 = 3,
       };
    }
    

    【讨论】:

    • 对象初始值设定项很好,但如果您有一些在创建实例时必须初始化的属性,它们不能替代构造函数参数。
    • 我看不出有什么不同。初始化集中在构造函数中,现在每次您想要完成该逻辑时,您都将使用此对象初始化语法。这没有回答问题。
    【解决方案5】:

    除了直接赋值之外,我能想到在构造函数中填充对象字段的简单方法。正如其他人所说,可能有一些使用反射的解决方法,但它甚至不接近手动填充字段的简单性。

    也许从构造函数填充类的字段而不向构造函数添加任何实际代码的一种方法是在构建后更改 IL 并添加您自己手动添加的代码。对于库或 NuGet 包,这可能是一种有趣的方法。

    除此之外,我没有看到用于从构造函数填充类字段的用例,而无需直接分配它们。如果您只有几个必填字段,那么构造函数的实现非常简单,因此不值得付出努力。如果您有一个带有很多参数的巨大构造函数,那么这看起来像是代码异味。您可以使用Object Initializer,这非常简单。

    【讨论】:

      【解决方案6】:

      不,主要是因为这种模式相当僵化。例如,如果您想要一个不会设置为字段的参数怎么办?那是什么语法?您可能想出的大多数解决方案要么是限制性的,要么与当前方法一样冗长。

      不过,这种刚性只是一般情况下的问题。如果这个模式适合你,那么你可以通过继承来定义它,这是适合这项工作的工具。

      因此,您可以定义一组可以继承自的通用 Tuple<> 类类:

      public abstract class TupleBase<T1>
      {
          public T1 Argument1 { get; private set; }
      
          public TupleBase(T1 argument1)
          {
             this.Argument1 = argument1;
          }
      }
      
      public abstract class TupleBase<T1, T2>
      {
          public T1 Argument1 { get; private set; }
          public T2 Argument2 { get; private set; }
      
          public TupleBase(T1 argument1, T2 argument2)
          {
             this.Argument1 = argument1;
             this.Argument2 = argument2;
          }
      }
      
      public abstract class TupleBase<T1, T2, T3>
      {
          public T1 Argument1 { get; private set; }
          public T2 Argument2 { get; private set; }
          public T3 Argument3 { get; private set; }
      
          public TupleBase(T1 argument1, T2 argument2, T3 argument3)
          {
             this.Argument1 = argument1;
             this.Argument2 = argument2;
             this.Argument3 = argument3;
          }
      }
      
      //  Etc..
      

      然后

      class MyClass
      {
          int var1;
          int var2;
          int var3;
          int var4;
      
          public MyClass(int var1, int var2, int var3, int var4){
              this.var1 = var1;
              this.var2 = var2;
              this.var3 = var3;
              this.var4 = var4;
          }
      }
      

      变成

      class MyClass : TupleBase<int, int, int, int>
      {
          public MyClass(int var1, int var2, int var3, int var4)
              :   base(var1, var2, var3, var4)
          {
          }
      }
      

      .

      继承是基本正确的工具,因为您希望MyClass 采用基本模式,即继承它。您在 C# 中遇到的问题是您一次只能从一个类继承,因此您不能在所有情况下都添加这样的附加功能。

      或者,你可以写

      class MyClass
      {
          int var1;
          int var2;
          int var3;
          int var4;
      
          public MyClass(int var1, int var2, int var3, int var4){
              this.SetConstructorValues(var1, var2, var3, var4);
          }
      }
      

      ,其中.SetConstructorValues() 是通用扩展方法

      private static System.Collections.ConcurrentDictionary<Type, Action<object, object[]>> ConstructorSetterDictionary { get; private set; }
      
      public static void SetConstructorValues<T_SetClass>(
                  this T_SetClass instanceToSetValuesFor
              ,   params object[] constructorArguments
          )
      {
          var instanceType = typeof(T_SetClass);
      
          Action<object, object[]> constructorSetterAction;
          if (!ConstructorSetterDictionary.TryGetValue(
                      instanceType
                  ,   out constructorSetterAction
              ))
          {
              throw new Exception("Populate the dictionary!  Also change this Exception message; it's from a StackOverflow example and not really designed to actually be written like this.");
          }
      
          constructorSetterAction(
                      instanceToSetValuesFor
                  ,   constructorArguments
              );
      }
      

      ,其中ConstructorSetterDictionary 是每个Type 的setter-Action&lt;object, object[]&gt; 的字典,根据您喜欢的任何逻辑推断(例如,将带有参数名称的构造函数与字段匹配),在程序启动时使用反射。

      从概念上讲,基于对象的Type 获取这样的方法基本上是虚拟方法的工作方式,其中ConstructorSetterDictionary 是虚拟查找表。

      我发现这种元编程在我自己的工作中很有用,但请注意,在某些时候,它不再是 C#。

      【讨论】:

        【解决方案7】:

        你可以使用元组“魔法”。

            class MyClass
            {
                int var1;
                int var2;
                int var3;
                int var4;
                public MyClass(int var1, int var2, int var3, int var4) => (this.var1, this.var2, this.var3, this.var4) = (var1, var2, var3, var4);
            }
        

        【讨论】:

          【解决方案8】:

          简单来说,你不能。使用 this 不是必需的,但是这样做很优雅,并且表明了加强变量是类上下文的一部分这一事实的意图。

          但是,这里的问题是因为您的参数和实例变量具有相同的名称。

          编译器无法区分同名变量(它可能会抱怨循环引用)。

          this 关键字的使用允许我们告诉编译器我们指的是该变量的当前实例。

          我认为您可以通过更好的变量命名方法来改进代码和编码本身。

          例如var1 var2 var3 他们什么都不说,使代码难以理解。

          尽量具体和详细: 例如名字、姓氏、地址等。它们是不言自明的。

          【讨论】:

            猜你喜欢
            • 2013-09-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多