Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
66
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

It is a good practice to use a default fallback language most people will understand, in case a translation is not available. I suggest to use English as default fallback language.

Recursive interpolation

Although not very useful, it is allowed to use an interpolated string recursively inside another's curly brackets:

Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:"); Console.WriteLine($"My class is called {nameof(MyClass)}.");

Output:

String has 27 chars:

My class is called MyClass.

Section 72.3: Auto-property initializers

Introduction

Properties can be initialized with the = operator after the closing }. The Coordinate class below shows the available options for initializing a property:

Version ≥ 6.0

public class Coordinate

{

public int X { get; set; } = 34; // get or set auto-property with initializer

public int Y { get; } = 89; // read-only auto-property with initializer

}

Accessors With Di erent Visibility

You can initialize auto-properties that have di erent visibility on their accessors. Here’s an example with a protected setter:

public string Name { get; protected set; } = "Cheeze";

The accessor can also be internal, internal protected, or private.

Read-Only Properties

In addition to flexibility with visibility, you can also initialize read-only auto-properties. Here’s an example:

public List<string> Ingredients { get; } =

new List<string> { "dough", "sauce", "cheese" };

This example also shows how to initialize a property with a complex type. Also, auto-properties can’t be write-only, so that also precludes write-only initialization.

GoalKicker.com – C# Notes for Professionals

423

Old style (pre C# 6.0)

Before C# 6, this required much more verbose code. We were using one extra variable called backing property for the property to give default value or to initialize the public property like below,

Version < 6.0

public class Coordinate

{

private int _x = 34;

public int X { get { return _x; } set { _x = value; } }

private readonly int _y = 89; public int Y { get { return _y; } }

private readonly int _z;

public int Z { get { return _z; } }

public Coordinate()

{

_z = 42;

}

}

Note: Before C# 6.0, you could still initialize read and write auto implemented properties (properties with a getter and a setter) from within the constructor, but you could not initialize the property inline with its declaration

View Demo

Usage

Initializers must evaluate to static expressions, just like field initializers. If you need to reference non-static members, you can either initialize properties in constructors like before, or use expression-bodied properties. Nonstatic expressions, like the one below (commented out), will generate a compiler error:

// public decimal X { get; set; } = InitMe(); // generates compiler error

decimal InitMe() { return 4m; }

But static methods can be used to initialize auto-properties:

public class Rectangle

{

public double Length { get; set; } = 1; public double Width { get; set; } = 1;

public double Area { get; set; } = CalculateArea(1, 1);

public static double CalculateArea(double length, double width)

{

return length * width;

}

}

This method can also be applied to properties with di erent level of accessors:

public short Type { get; private set; } = 15;

The auto-property initializer allows assignment of properties directly within their declaration. For read-only

GoalKicker.com – C# Notes for Professionals

424

properties, it takes care of all the requirements required to ensure the property is immutable. Consider, for example, the FingerPrint class in the following example:

public class FingerPrint

{

public DateTime TimeStamp { get; } = DateTime.UtcNow;

public string User { get; } = System.Security.Principal.WindowsPrincipal.Current.Identity.Name;

public string Process { get; } = System.Diagnostics.Process.GetCurrentProcess().ProcessName;

}

View Demo

Cautionary notes

Take care to not confuse auto-property or field initializers with similar-looking expression-body methods which make use of => as opposed to =, and fields which do not include { get; }.

For example, each of the following declarations are di erent.

public class UserGroupDto

{

// Read-only auto-property with initializer:

public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();

// Read-write field with initializer:

public ICollection<UserDto> Users2 = new HashSet<UserDto>();

// Read-only auto-property with expression body:

public ICollection<UserDto> Users3 => new HashSet<UserDto>();

}

Missing { get; } in the property declaration results in a public field. Both read-only auto-property Users1 and read-write field Users2 are initialized only once, but a public field allows changing collection instance from outside the class, which is usually undesirable. Changing a read-only auto-property with expression body to read-only property with initializer requires not only removing > from =>, but adding { get; }.

The di erent symbol (=> instead of =) in Users3 results in each access to the property returning a new instance of the HashSet<UserDto> which, while valid C# (from the compiler's point of view) is unlikely to be the desired behavior when used for a collection member.

The above code is equivalent to:

public class UserGroupDto

{

//This is a property returning the same instance

//which was created when the UserGroupDto was instantiated. private ICollection<UserDto> _users1 = new HashSet<UserDto>(); public ICollection<UserDto> Users1 { get { return _users1; } }

//This is a field returning the same instance

//which was created when the UserGroupDto was instantiated.

public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();

GoalKicker.com – C# Notes for Professionals

425

//This is a property which returns a new HashSet<UserDto> as

//an ICollection<UserDto> on each call to it.

public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }

}

Section 72.4: Null propagation

The ?. operator and ?[...] operator are called the null-conditional operator. It is also sometimes referred to by other names such as the safe navigation operator.

This is useful, because if the . (member accessor) operator is applied to an expression that evaluates to null, the program will throw a NullReferenceException. If the developer instead uses the ?. (null-conditional) operator, the expression will evaluate to null instead of throwing an exception.

Note that if the ?. operator is used and the expression is non-null, ?. and . are equivalent.

Basics

var teacherName = classroom.GetTeacher().Name;

// throws NullReferenceException if GetTeacher() returns null

View Demo

If the classroom does not have a teacher, GetTeacher() may return null. When it is null and the Name property is accessed, a NullReferenceException will be thrown.

If we modify this statement to use the ?. syntax, the result of the entire expression will be null:

var teacherName = classroom.GetTeacher()?.Name;

// teacherName is null if GetTeacher() returns null

View Demo

Subsequently, if classroom could also be null, we could also write this statement as:

var teacherName = classroom?.GetTeacher()?.Name;

// teacherName is null if GetTeacher() returns null OR classroom is null

View Demo

This is an example of short-circuiting: When any conditional access operation using the null-conditional operator evaluates to null, the entire expression evaluates to null immediately, without processing the rest of the chain.

When the terminal member of an expression containing the null-conditional operator is of a value type, the expression evaluates to a Nullable<T> of that type and so cannot be used as a direct replacement for the expression without ?..

bool hasCertification = classroom.GetTeacher().HasCertification;

// compiles without error but may throw a NullReferenceException at runtime

bool hasCertification = classroom?.GetTeacher()?.HasCertification;

// compile time error: implicit conversion from bool? to bool not allowed

bool? hasCertification = classroom?.GetTeacher()?.HasCertification;

// works just fine, hasCertification will be null if any part of the chain is null

GoalKicker.com – C# Notes for Professionals

426

bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();

// must extract value from nullable to assign to a value type variable

Use with the Null-Coalescing Operator (??)

You can combine the null-conditional operator with the Null-coalescing Operator (??) to return a default value if the expression resolves to null. Using our example above:

var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";

//teacherName will be "No Name" when GetTeacher()

//returns null OR classroom is null OR Name is null

Use with Indexers

The null-conditional operator can be used with indexers:

var firstStudentName = classroom?.Students?[0]?.Name;

In the above example:

The first ?. ensures that classroom is not null.

The second ? ensures that the entire Students collection is not null.

The third ?. after the indexer ensures that the [0] indexer did not return a null object. It should be noted that this operation can still throw an IndexOutOfRangeException.

Use with void Functions

Null-conditional operator can also be used with void functions. However in this case, the statement will not evaluate to null. It will just prevent a NullReferenceException.

List<string> list = null;

list?.Add("hi"); // Does not evaluate to null

Use with Event Invocation

Assuming the following event definition:

private event EventArgs OnCompleted;

When invoking an event, traditionally, it is best practice to check if the event is null in case no subscribers are present:

var handler = OnCompleted; if (handler != null)

{

handler(EventArgs.Empty);

}

Since the null-conditional operator has been introduced, the invocation can be reduced to a single line:

OnCompleted?.Invoke(EventArgs.Empty);

Limitations

GoalKicker.com – C# Notes for Professionals

427

Null-conditional operator produces rvalue, not lvalue, that is, it cannot be used for property assignment, event subscription etc. For example, the following code will not work:

//Error: The left-hand side of an assignment must be a variable, property or indexer

Process.GetProcessById(1337)?.EnableRaisingEvents = true;

//Error: The event can only appear on the left hand side of += or -=

Process.GetProcessById(1337)?.Exited += OnProcessExited;

Gotchas

Note that:

int? nameLength = person?.Name.Length;

// safe if 'person' is null

 

 

is not the same as:

int? nameLength = (person?.Name).Length; // avoid this

because the former corresponds to:

int? nameLength = person != null ? (int?)person.Name.Length : null;

and the latter corresponds to:

int? nameLength = (person != null ? person.Name : null).Length;

Despite ternary operator ?: is used here for explaining the di erence between two cases, these operators are not equivalent. This can be easily demonstrated with the following example:

void Main()

{

var foo = new Foo(); Console.WriteLine("Null propagation"); Console.WriteLine(foo.Bar?.Length);

Console.WriteLine("Ternary");

Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);

}

class Foo

{

public string Bar

{

get

{

Console.WriteLine("I was read"); return string.Empty;

}

}

}

Which outputs:

Null propagation I was read

0

GoalKicker.com – C# Notes for Professionals

428