Есть ли эквивалент списка инициализаторов в С#?

В C++ вы можете определить конструктор следующим образом:

class Foo
{
    private:
        int x, y;
    public:
        Foo(int _x, int _y) : x(_x), y(_y)
        {
        }
};

Конструктор автоматически инициализирует x до _x и y до _y. Это удобно, когда поля инициализируются просто как параметры конструктора.

Вы ищете первичные конструкторы?

MakePeaceGreatAgain 17.05.2024 13:21

Да, но это кажется более ограниченным, чем в C++.

being addicted to coding 17.05.2024 13:56
convenient это? Подозреваю, что здесь ошибочное мнение. Поля в C# представляют собой лишь сведения о внутреннем состоянии. Свойства — это не просто геттеры/сеттеры. Только свойства являются частью поверхности типа и обрабатываются сериализаторами, привязкой данных, частью интерфейсов и т. д. Конструкторы как в C++, так и в C# используются для передачи значений инициализации «свойств», а не для передачи внутреннего невидимого состояния. Если бы у вас был Foo {public int X{get;set;} public int Y {get;set;}}, вы могли бы использовать new Foo{X=x, Y=y}. Вы можете получить свойства, доступные только для чтения, с помощью public int X {get;init;}
Panagiotis Kanavos 17.05.2024 13:57

@beingaddictedtocoding more limited than that in C++ за что? На основании каких критериев? Кстати, если вы используете записи, например public record Foo(int X,int Y); (ничего больше, только эта строка), вы автоматически получаете класс только для чтения со свойствами X и Y только для инициализации, деконструкцией, семантикой копирования при записи с ключевым словом with. Важны свойства, а не поля

Panagiotis Kanavos 17.05.2024 14:00

В C++ вы, возможно, можете определить несколько списков инициализаторов, но, похоже, невозможно определить несколько первичных конструкторов.

being addicted to coding 17.05.2024 14:25

Автоматически? Похоже, вы вручную инициализировали x для x_.

Wyck 17.05.2024 14:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
96
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В версиях С# до 12 вы должны сделать:

class Foo
{
  int _x;
  int _y;
  public Foo(int x, int y) {
   _x = x;
   _y = y;
  }
}

С С# 12 вы можете сделать:

class Foo (int x, int y);

и x и y будут доступны для всего экземпляра вашего класса.

Очевидно, что ваш класс будет иметь больше логики и тело типа:

class Foo (int x, int y)
{
  // Logic that works with x and y
}

Другой тип в C# называется record, который также предоставляет параметр как общедоступное свойство. Так:

record Foo (int X, int Y);

Foo foo = new (10, 20);

// foo.X will be 10 and foo.Y will be 20

В первом примере (до 12), если вы поменяете роли идентификаторов с подчеркиванием _ и без него, чтобы получить class Foo { int x; int y; public Foo(int _x, int _y) { x = _x; y = _y; } }, интересно, будет ли это ближе к коду C++, заданному спрашивающим? Конечно, это может противоречить широко распространенным соглашениям об именах в C#.

Jeppe Stig Nielsen 17.05.2024 14:38

@JeppeStigNielsen, это тоже была моя мысль, но затем я решил представить код в соглашении об именах C#, чтобы спрашивающий также ознакомился с этим соглашением, поскольку похоже, что они находятся на этапе склонения. :)

Rojan Gh. 17.05.2024 17:04

Этот синтаксис не очень полезен. В C# (и .NET в целом) поля — это просто внутреннее состояние. Свойства — это не просто методы получения и установки, они являются частью программного интерфейса типа. Интерфейсы и наследование работают с типами, а не с полями. Сериализаторы и привязка данных работают с типами.

Класс C# будет выглядеть так:

class Foo
{
    public int X {get;set}
    public int Y {get;set;}
}

или

class Foo
{
    public int X {get;init}
    public int Y {get;init;}
}

Инициализация объекта

Оба типа свойств позволяют легко создавать и инициализировать экземпляры всего лишь с помощью new Foo{X=x,Y=y};.

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

class Foo
{
    public int X {get;init;} =0;
    public int Y {get;init;} =0;
}

var x=new Foo(){Y=5};

Рекорды

Гораздо более мощным эквивалентом константного класса в C++ были бы записи, а не класс с полями:

public record Foo(int X,int Y);

Это позволяет писать код типа:

var foo=new Foo(1,1);
var foo2 = foo with {Y=5};
var foo3 = foo2 with {Y=1};
var (x,y)=foo2;

Assert.Equals(foo,foo3);

Во время компиляции компилятор генерирует автоматические свойства только для инициализации, конструктор, оператор равенства значений и операторы клонирования:


[NullableContext(1)]
[Nullable(0)]
public class Foo : IEquatable<Foo>
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly int <X>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly int <Y>k__BackingField;

    [CompilerGenerated]
    protected virtual Type EqualityContract
    {
        [CompilerGenerated]
        get
        {
            return typeof(Foo);
        }
    }

    public int X
    {
        [CompilerGenerated]
        get
        {
            return <X>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <X>k__BackingField = value;
        }
    }

    public int Y
    {
        [CompilerGenerated]
        get
        {
            return <Y>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <Y>k__BackingField = value;
        }
    }

    public Foo(int X, int Y)
    {
        <X>k__BackingField = X;
        <Y>k__BackingField = Y;
        base..ctor();
    }

    [CompilerGenerated]
    public override string ToString()
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("Foo");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder))
        {
            stringBuilder.Append(' ');
        }
        stringBuilder.Append('}');
        return stringBuilder.ToString();
    }

    [CompilerGenerated]
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        builder.Append("X = ");
        builder.Append(X.ToString());
        builder.Append(", Y = ");
        builder.Append(Y.ToString());
        return true;
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public static bool operator !=(Foo left, Foo right)
    {
        return !(left == right);
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public static bool operator ==(Foo left, Foo right)
    {
        return (object)left == right || ((object)left != null && left.Equals(right));
    }

    [CompilerGenerated]
    public override int GetHashCode()
    {
        return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<X>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<Y>k__BackingField);
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public override bool Equals(object obj)
    {
        return Equals(obj as Foo);
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public virtual bool Equals(Foo other)
    {
        return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(<X>k__BackingField, other.<X>k__BackingField) && EqualityComparer<int>.Default.Equals(<Y>k__BackingField, other.<Y>k__BackingField));
    }

    [CompilerGenerated]
    public virtual Foo <Clone>$()
    {
        return new Foo(this);
    }

    [CompilerGenerated]
    protected Foo(Foo original)
    {
        <X>k__BackingField = original.<X>k__BackingField;
        <Y>k__BackingField = original.<Y>k__BackingField;
    }

    [CompilerGenerated]
    public void Deconstruct(out int X, out int Y)
    {
        X = this.X;
        Y = this.Y;
    }
}

Первичные конструкторы

В C# 12 синтаксис конструктора записей стал доступен всем классам. Теперь можно написать:

public class C(int x,int y) 
{
    public int X {get;}=x;
    public int Y {get;}=y;
}

И даже

public class C(int x,int y) 
{
    public int X {get;init;} = x;
    public int Y {get;init;} = y;
}

Что позволяет сделать несколько необычное:

var new C(1,3){X=5};

Это может быть полезно, например, для переопределения значения по умолчанию свойства, доступного только для инициализации:

public class C(int x,int y) 
{
    public int X {get;init;} = x;
    public int Y {get;init;} = y;
    public int Z {get;init;} = 0;
}

...
var x=new C(1,4){ Z=2};

Должен ли я использовать инициализатор объекта, а не основной конструктор? Я думал, что инициализатор объекта предназначен для дополнительной настройки после того, как объект правильно инициализируется конструктором.

being addicted to coding 17.05.2024 16:01

@beingaddictedtocoding вы можете сделать и то, и другое, если хотите. Я добавил обновление, показывающее, как можно использовать первичные конструкторы для установки свойств почти в том же синтаксисе, что и в C++. Выбранный вами синтаксис зависит от ваших требований и ограничений, которые вы хотите применить к значениям. В C++ довольно часто встречаются константные классы. Эквивалентом является полный класс record.

Panagiotis Kanavos 20.05.2024 09:16

Другие вопросы по теме