В C++ вы можете определить конструктор следующим образом:
class Foo
{
private:
int x, y;
public:
Foo(int _x, int _y) : x(_x), y(_y)
{
}
};
Конструктор автоматически инициализирует x
до _x
и y
до _y
.
Это удобно, когда поля инициализируются просто как параметры конструктора.
Да, но это кажется более ограниченным, чем в C++.
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;}
@beingaddictedtocoding more limited than that in C++
за что? На основании каких критериев? Кстати, если вы используете записи, например public record Foo(int X,int Y);
(ничего больше, только эта строка), вы автоматически получаете класс только для чтения со свойствами X
и Y
только для инициализации, деконструкцией, семантикой копирования при записи с ключевым словом with
. Важны свойства, а не поля
В C++ вы, возможно, можете определить несколько списков инициализаторов, но, похоже, невозможно определить несколько первичных конструкторов.
Автоматически? Похоже, вы вручную инициализировали x
для x_
.
В версиях С# до 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#.
@JeppeStigNielsen, это тоже была моя мысль, но затем я решил представить код в соглашении об именах C#, чтобы спрашивающий также ознакомился с этим соглашением, поскольку похоже, что они находятся на этапе склонения. :)
Этот синтаксис не очень полезен. В 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};
Должен ли я использовать инициализатор объекта, а не основной конструктор? Я думал, что инициализатор объекта предназначен для дополнительной настройки после того, как объект правильно инициализируется конструктором.
@beingaddictedtocoding вы можете сделать и то, и другое, если хотите. Я добавил обновление, показывающее, как можно использовать первичные конструкторы для установки свойств почти в том же синтаксисе, что и в C++. Выбранный вами синтаксис зависит от ваших требований и ограничений, которые вы хотите применить к значениям. В C++ довольно часто встречаются константные классы. Эквивалентом является полный класс record
.
Вы ищете первичные конструкторы?