Как сохранить все параметры ctor в полях

Я изучаю С#, и при кодировании возникла мысль. Можно ли автоматически сохранять параметры из конструктора в поля без необходимости писать 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 и сохранить все переменные в полях, если имена совпадают?

Ответ 1

Если вы сначала определили свои переменные, вы можете использовать инструмент "Быстрые действия" в Visual Studio, чтобы сгенерировать конструктор для вас; это дает вам выбор определенных в настоящее время полей класса для включения.

с помощью этого будет вставлен класс конструктора со всеми выбранными вами полями в качестве параметров, и он будет присваивать значения полям.

Это не уменьшит объем кода, но сократит количество набираемого текста.

Ответ 2

Нет, в текущей версии С# нет способа сделать это проще. Для решения этой проблемы в предварительных выпусках С# 6.0 появилась новая функция под названием "Основные конструкторы", но она была удалена до окончательного выпуска. https://www.c-sharpcorner.com/UploadFile/7ca517/primary-constructor-is-removed-from-C-Sharp-6-0/

В настоящее время я считаю, что команда С# работает над добавлением записей в язык: https://github.com/dotnet/roslyn/blob/features/records/docs/features/records.md - это должно упростить работу с простыми классами данных, как в F #

Ответ 3

Коротко: нет, долго: да, есть взлом.

Для этого вы можете использовать сочетание отражения и сохранения параметра во временном массиве.

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

Проще говоря, вы не можете. Использование этого не обязательно, но это элегантно и демонстрирует намерение подчеркнуть тот факт, что переменная является частью контекста класса.

Однако проблема здесь заключается в том, что и ваши параметры, и переменные экземпляра имеют одинаковые имена.

Компилятор не может отличить одноименные переменные (это может быть жалобой на циклическую ссылку).

Использование ключевого слова this позволяет нам сообщить компилятору, что мы ссылаемся на текущий экземпляр этой переменной.

Я думаю, что вы можете улучшить код и кодирование как таковое с помощью лучшего подхода к именованию для ваших переменных.

Например, var1 var2 var3 они ничего не говорят и делают код трудным для понимания.

Попробуйте быть конкретным и многословным: Например, firstName, lastName, адрес и т.д. Они говорят сами за себя.

Ответ 6

Я могу думать о нелегком способе заполнения полей объекта внутри конструктора, кроме как назначать их напрямую. Как уже говорили другие, могут быть некоторые обходные пути с использованием отражения, но это даже не близко к простоте заполнения полей вручную.

Возможно, один из способов заполнения полей класса из конструктора без добавления какого-либо фактического кода в конструктор - это изменить IL после сборки и добавить код, который вы добавили бы вручную. Это может быть интересным подходом для библиотеки или пакета NuGet.

Кроме этого, я не вижу вариантов использования для заполнения полей класса из конструктора без их непосредственного назначения. Если у вас есть только несколько обязательных полей, конструктор довольно прост в реализации, поэтому он не будет стоить усилий. Если у вас огромный конструктор с большим количеством параметров, то это похоже на запах кода. Вы можете использовать Object Initializer, который довольно прост.

Ответ 7

Нет, во многом потому, что эта модель довольно жесткая. Например, что, если вам нужен аргумент, который не будет установлен в поле; какой будет синтаксис для этого? Большинство решений, которые вы можете придумать, будут либо ограничивающими, либо такими же многословными, как и нынешний подход.

Тем не менее, эта жесткость является проблемой только в общем случае. Если этот шаблон работает для вас, вы можете определить его с помощью наследования, которое является подходящим инструментом для этой работы.

Таким образом, вы можете определить набор общих классов Tuple<> -like, от которых вы можете наследовать:

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 использовал базовый шаблон, то есть наследовал его. Проблема, с которой вы столкнетесь в С#, заключается в том, что вы можете наследовать только от одного класса за раз, поэтому вы не можете просто использовать такие дополнительные функции во всех случаях.

Кроме того, вы можете написать

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 from a Qaru example and not really designed to actually be written like this.");
    }

    constructorSetterAction(
                instanceToSetValuesFor
            ,   constructorArguments
        );
}

где ConstructorSetterDictionary - это словарь setter- Action<object, object[]> для каждого Type, выведенный в соответствии с любой логикой (например, сопоставление конструкторов с именами параметров для полей), заполняемый при запуске программы с использованием отражения.

Концептуально, получение такого метода на основе объекта Type - это, в основном, работа виртуальных методов, где ConstructorSetterDictionary - это таблица виртуального поиска.

Я считаю этот вид метапрограммирования полезным в моей собственной работе, но предупреждаю, что в какой-то момент он перестает быть С#.

Ответ 8

Вы можете использовать набор "волшебство".

    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);
    }