Поиск…


Вступление

Ключевые слова предопределены, зарезервированные идентификаторы со специальным значением для компилятора. Они не могут использоваться в качестве идентификаторов в вашей программе без префикса @ . Например, @if является юридическим идентификатором, но не ключевым словом if .

замечания

C # имеет предопределенную коллекцию «ключевых слов» (или зарезервированных слов), каждая из которых имеет специальную функцию. Эти слова нельзя использовать в качестве идентификаторов (имена для переменных, методов, классов и т. Д.), Если только не префикс @ .

Помимо них, C # также использует некоторые ключевые слова, чтобы обеспечить конкретное значение кода. Они называются контекстуальными ключевыми словами. Контекстные ключевые слова могут использоваться как идентификаторы и не должны иметь префикс @ при использовании в качестве идентификаторов.

stackalloc

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

//Allocate 1024 bytes. This returns a pointer to the first byte.
byte* ptr = stackalloc byte[1024];

//Assign some values...
ptr[0] = 109;
ptr[1] = 13;
ptr[2] = 232;
...

Используется в небезопасном контексте.

Как и во всех указателях на C #, проверки чтения и присваивания не проверяются. Чтение за пределами выделенной памяти будет иметь непредсказуемые результаты - он может получить доступ к произвольному местоположению в памяти или может вызвать исключение нарушения доступа.

//Allocate 1 byte
byte* ptr = stackalloc byte[1];

//Unpredictable results...
ptr[10] = 1;
ptr[-1] = 2;

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

unsafe IntPtr Leak() {
    //Allocate some memory on the stack
    var ptr = stackalloc byte[1024];

    //Return a pointer to that memory (this exits the scope of "Leak")
    return new IntPtr(ptr);
}

unsafe void Bad() {
     //ptr is now an invalid pointer, using it in any way will have
     //unpredictable results. This is exactly the same as accessing beyond
     //the bounds of the pointer.
     var ptr = Leak();
}

stackalloc может использоваться только при объявлении и инициализации переменных. Ниже не действует:

byte* ptr;
...
ptr = stackalloc byte[1024];

Примечания:

stackalloc следует использовать только для оптимизации производительности (либо для вычисления, либо для взаимодействия). Это связано с тем, что:

  • Сборщик мусора не требуется, поскольку память распределяется в стеке, а не в кучу - память освобождается, как только переменная выходит из области видимости
  • Быстрее выделять память в стеке, а не кучу
  • Увеличьте вероятность попадания кеша на процессор из-за локальности данных

летучий

Добавление ключевого слова volatile в поле указывает компилятору, что значение поля может быть изменено несколькими отдельными потоками. Основной целью ключевого слова volatile является предотвращение оптимизации компилятора, предполагающего только однопоточный доступ. Использование volatile гарантирует, что значение поля является самым последним значением, которое доступно, и значение не подлежит кешированию, которое является энергонезависимым значением.

Рекомендуется отметить каждую переменную, которая может использоваться несколькими потоками как volatile чтобы предотвратить непредвиденное поведение из-за закулисных оптимизаций. Рассмотрим следующий блок кода:

public class Example
{
    public int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler will optimize this to y = 15
        var y = x + 10;

        /* the value of x will always be the current value, but y will always be "15" */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

В приведенном выше кодовом блоке компилятор читает операторы x = 5 и y = x + 10 и определяет, что значение y всегда будет равным 15. Таким образом, он будет оптимизировать последний оператор как y = 15 . Однако переменная x фактически является public полем, а значение x может быть изменено во время выполнения через другой поток, действующий на это поле отдельно. Теперь рассмотрим этот модифицированный блок кода. Обратите внимание, что поле x теперь объявлено volatile .

public class Example
{
    public volatile int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler no longer optimizes this statement
        var y = x + 10;

        /* the value of x and y will always be the correct values */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

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

volatile может использоваться только для полей в class es или struct s. Ниже не действует :

public void MyMethod()
{
    volatile int x;
}

volatile может применяться только к полям следующих типов:

  • ссылочные типы или типовые параметры, известные как ссылочные типы
  • примитивные типы, такие как sbyte , byte , short , ushort , int , uint , char , float и bool
  • типы перечислений на основе byte , sbyte , short , ushort , int или uint
  • IntPtr и UIntPtr

Примечания:

  • Модификатор volatile обычно используется для поля, к которому обращаются несколько потоков, без использования оператора блокировки для сериализации доступа.
  • Ключевое слово volatile может применяться к полям ссылочных типов
  • Ключевое слово volatile не будет работать на 64-битных примитивах на 32-битной платформе Atom. Блокированные операции, такие как Interlocked.Read и Interlocked.Exchange все равно должны использоваться для безопасного многопоточного доступа на этих платформах.

фиксированный

Фиксированный оператор фиксирует память в одном месте. Объекты в памяти обычно движутся вокруг, что делает сборку мусора возможным. Но когда мы используем небезопасные указатели на адреса памяти, эта память не должна перемещаться.

  • Мы используем фиксированный оператор, чтобы убедиться, что сборщик мусора не перемещает строковые данные.

Фиксированные переменные

var myStr = "Hello world!";

fixed (char* ptr = myStr)
{
    // myStr is now fixed (won't be [re]moved by the Garbage Collector).
    // We can now do something with ptr.
}

Используется в небезопасном контексте.

Размер фиксированного массива

unsafe struct Example
{
    public fixed byte SomeField[8];
    public fixed char AnotherField[64];
}

fixed может использоваться только для полей в struct (также должен использоваться в небезопасном контексте).

дефолт

Для классов, интерфейсов, делегатов, массивов, с нулевым значением (например, int?) И типов указателей по default(TheType) возвращается значение null :

class MyClass {}
Debug.Assert(default(MyClass) == null);
Debug.Assert(default(string) == null);

Для structs и default(TheType) возвращает то же, что и new TheType() :

struct Coordinates
{
    public int X { get; set; }
    public int Y { get; set; }
}

struct MyStruct
{
    public string Name { get; set; }
    public Coordinates Location { get; set; }
    public Coordinates? SecondLocation { get; set; }
    public TimeSpan Duration { get; set; }
}

var defaultStruct = default(MyStruct);
Debug.Assert(defaultStruct.Equals(new MyStruct()));
Debug.Assert(defaultStruct.Location.Equals(new Coordinates()));
Debug.Assert(defaultStruct.Location.X == 0);
Debug.Assert(defaultStruct.Location.Y == 0);
Debug.Assert(defaultStruct.SecondLocation == null);
Debug.Assert(defaultStruct.Name == null);
Debug.Assert(defaultStruct.Duration == TimeSpan.Zero);

default(T) может быть особенно полезно, когда T является общим параметром, для которого не существует ограничений, чтобы определить, является ли T ссылочным типом или типом значения, например:

public T GetResourceOrDefault<T>(string resourceName)
{
   if (ResourceExists(resourceName))
   {
      return (T)GetResource(resourceName);
   }
   else
   {
      return default(T);
   }
}

только для чтения

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

Ключевое слово readonly отличается от ключевого слова const . Поле const может быть инициализировано только при объявлении поля. Поле readonly может быть инициализировано либо в объявлении, либо в конструкторе. Поэтому поля readonly могут иметь разные значения в зависимости от используемого конструктора.

Ключевое слово readonly часто используется при инъекции зависимостей.

class Person
{
    readonly string _name;
    readonly string _surname = "Surname";

    Person(string name)
    {
        _name = name;
    }
    void ChangeName()
    {
        _name = "another name"; // Compile error
        _surname = "another surname"; // Compile error
    }
}

Примечание. Объявление поля readonly не подразумевает неизменность . Если поле является ссылочным типом, то содержимое объекта может быть изменено. Readonly обычно используется для предотвращения перезаписи и назначения объекта только во время создания экземпляра этого объекта.

Примечание. Внутри конструктора поле readonly можно переназначить

public class Car
{
    public double Speed {get; set;}
}

//In code

private readonly Car car = new Car();

private void SomeMethod()
{
    car.Speed = 100;
}

как

Ключевое слово as - это оператор, похожий на приведение . Если бросок невозможен, использование создает as null а не приводит к InvalidCastException .

expression as type эквивалентно expression is type ? (type)expression : (type)null с оговоркой , что , as действует только на ссылочные преобразования, обнуляемого преобразования и преобразования бокса. Определенные пользователем преобразования не поддерживаются; вместо этого следует использовать обычный литой состав.

Для расширения выше компилятор генерирует код таким образом, что expression будет оцениваться только один раз и использовать проверку одного динамического типа (в отличие от двух в примере выше).

as может быть полезно при ожидании аргументации для облегчения нескольких типов. В частности , она предоставляет многочисленные пользовательские опции - вместо того , чтобы проверять каждую возможность с is перед заливкой, или просто литья и отлова исключения. Лучше всего использовать «как» при кастинге / проверке объекта, который вызовет только одно неуправляемое наказание. Использование is в проверке, тогда литье приведет к двум неуправляемым штрафам.

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

Так как вызов , as может привести к null , всегда проверить результат , чтобы избежать NullReferenceException .

Пример использования

object something = "Hello";
Console.WriteLine(something as string);        //Hello
Console.Writeline(something as Nullable<int>); //null
Console.WriteLine(something as int?);          //null

//This does NOT compile:
//destination type must be a reference type (or a nullable value type)
Console.WriteLine(something as int);

Живая демонстрация на .NET скрипке

Эквивалентный пример без использования , as :

Console.WriteLine(something is string ? (string)something : (string)null);

Это полезно при переопределении функции Equals в пользовательских классах.

class MyCustomClass
{

    public override bool Equals(object obj)
    {
        MyCustomClass customObject = obj as MyCustomClass;

        // if it is null it may be really null
        // or it may be of a different type
        if (Object.ReferenceEquals(null, customObject))
        {
            // If it is null then it is not equal to this instance.
            return false;
        }

        // Other equality controls specific to class
    }

}

является

Проверяет, совместим ли объект с данным типом, то есть, если объект является экземпляром типа BaseInterface или типом, который происходит от BaseInterface :

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True
Console.WriteLine(d is BaseClass);     // True
Console.WriteLine(d is BaseInterface); // True
Console.WriteLine(d is object);        // True
Console.WriteLine(d is string);        // False

var b = new BaseClass();
Console.WriteLine(b is DerivedClass);  // False
Console.WriteLine(b is BaseClass);     // True
Console.WriteLine(b is BaseInterface); // True
Console.WriteLine(b is object);        // True
Console.WriteLine(b is string);        // False

Если целью броска является использование объекта, лучше всего использовать ключевое слово « as »,

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True - valid use of 'is'
Console.WriteLine(d is BaseClass);     // True - valid use of 'is'

if(d is BaseClass){
    var castedD = (BaseClass)d;
    castedD.Method(); // valid, but not best practice
}

var asD = d as BaseClass;

if(asD!=null){
    asD.Method(); //prefered method since you incur only one unboxing penalty
}

Но из функции pattern matching C # 7 расширяет оператор is для проверки типа и объявления новой переменной в одно и то же время. Эта же часть кода с C # 7:

7,0
if(d is BaseClass asD ){
    asD.Method();
}

тип

Возвращает Type объекта без необходимости его экземпляра.

Type type = typeof(string);
Console.WriteLine(type.FullName); //System.String
Console.WriteLine("Hello".GetType() == type); //True
Console.WriteLine("Hello".GetType() == typeof(string)); //True

Const

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

Например, поскольку скорость света никогда не изменится, мы можем сохранить его в константе.

const double c = 299792458;  // Speed of light

double CalculateEnergy(double mass)
{
    return mass * c * c;
}

Это по существу то же самое, что и return mass * 299792458 * 299792458 , поскольку компилятор будет напрямую подставлять c своим постоянным значением.

В результате c не может быть изменен после объявления. Ниже приведена ошибка времени компиляции:

const double c = 299792458;  // Speed of light 

c = 500;  //compile-time error

Константа может иметь префикс с теми же модификаторами доступа, что и методы:

private const double c = 299792458;
public const double c = 299792458;
internal const double c = 299792458;

const элементы static по своей природе. Однако использование static явно запрещено.

Вы также можете определить методы-локальные константы:

double CalculateEnergy(double mass)
{
    const c = 299792458;
    return mass * c * c;
}

Они не могут иметь префикса с private или public ключевым словом, поскольку они неявно локальны для метода, в котором они определены.


Не все типы могут использоваться в объявлении const . Допустимыми типами значений являются предопределенные типы sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double , decimal , bool и все типы enum . Пытаться объявить const члены с другими типами значений (например, TimeSpan или Guid ) не удастся во время компиляции.

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


Поскольку значения const известны во время компиляции, они разрешены как ярлыки case в инструкции switch , в качестве стандартных аргументов для необязательных параметров, в качестве аргументов спецификаций атрибутов и т. Д.


Если значения const используются для разных сборок, следует соблюдать осторожность при управлении версиями. Например, если сборка А определяет public const int MaxRetries = 3; , а сборка B использует эту константу, то, если значение MaxRetries позже будет изменено на 5 в сборке A (которое затем будет скомпилировано), это изменение не будет эффективным в сборке B, если сборка B также не будет скомпилирована (с ссылка на новую версию A).

По этой причине, если значение может измениться в будущих версиях программы, и если значение должно быть общедоступным, не объявляйте это значение const если не знаете, что все зависимые сборки будут перекомпилированы всякий раз, когда что-то изменится. Альтернативой является использование static readonly вместо const , которое разрешено во время выполнения.

Пространство имен

Ключевое слово namespace - это организационная конструкция, которая помогает нам понять, как устроена кодовая база. Пространства имен в C # являются виртуальными пространствами, а не находятся в физической папке.

namespace StackOverflow
{
    namespace Documentation
    {
        namespace CSharp.Keywords
        {
            public class Program
            {
                public static void Main()
                {
                    Console.WriteLine(typeof(Program).Namespace);
                    //StackOverflow.Documentation.CSharp.Keywords
                }
            }
        }
    }
}

Пространства имен в C # также могут быть записаны в цепочке синтаксиса. Ниже приведено следующее:

namespace StackOverflow.Documentation.CSharp.Keywords
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine(typeof(Program).Namespace);
            //StackOverflow.Documentation.CSharp.Keywords
        }
    }
}

попытаться, поймать, наконец, бросить

try , catch , finally , и throw разрешите вам обрабатывать исключения в вашем коде.

var processor = new InputProcessor();

// The code within the try block will be executed. If an exception occurs during execution of
// this code, execution will pass to the catch block corresponding to the exception type.
try 
{
    processor.Process(input);
}
// If a FormatException is thrown during the try block, then this catch block
// will be executed.
catch (FormatException ex)
{
    // Throw is a keyword that will manually throw an exception, triggering any catch block that is
    // waiting for that exception type. 
    throw new InvalidOperationException("Invalid input", ex);
}
// catch can be used to catch all or any specific exceptions. This catch block,
// with no type specified, catches any exception that hasn't already been caught
// in a prior catch block.
catch
{
    LogUnexpectedException(); 
    throw; // Re-throws the original exception.
}
// The finally block is executed after all try-catch blocks have been; either after the try has
// succeeded in running all commands or after all exceptions have been caught. 
finally
{
    processor.Dispose();
}

Примечание. Ключевое слово return можно использовать в блоке try , и блок finally все равно будет выполнен (как раз перед возвратом). Например:

try 
{
    connection.Open();
    return connection.Get(query);
} 
finally 
{
    connection.Close();
}

Заявление connection.Close() будет выполняться до результата connection.Get(query) Возвращается connection.Get(query) .

Продолжить

Сразу же передайте управление следующей итерации конструкции замкнутого контура (for, foreach, do, while):

for (var i = 0; i < 10; i++)
{
    if (i < 5)
    {
        continue;
    }
    Console.WriteLine(i);
}

Выход:

5
6
7
8
9

Живая демонстрация на .NET скрипке

var stuff = new [] {"a", "b", null, "c", "d"};

foreach (var s in stuff)
{
    if (s == null)
    {
        continue;
    }           
    Console.WriteLine(s);
}

Выход:


б
с
d

Живая демонстрация на .NET скрипке

ref, out

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

int x = 5;
ChangeX(ref x);
// The value of x could be different now

Для ссылочных типов экземпляр в переменной может быть изменен не только (как в случае без ref ), но также может быть полностью заменен:

Address a = new Address();
ChangeFieldInAddress(a);
// a will be the same instance as before, even if it is modified
CreateANewInstance(ref a);
// a could be an entirely new instance now

Основное различие между out и ref ключевым словом , что ref требует переменной для инициализации вызывающего, в то время как out передает , что ответственность перед вызываемым.

Чтобы использовать параметр out , и определение метода, и метод вызова должны явно использовать ключевое слово out .

int number = 1;
Console.WriteLine("Before AddByRef: " + number); // number = 1
AddOneByRef(ref number);
Console.WriteLine("After AddByRef: " + number);  // number = 2
SetByOut(out number);
Console.WriteLine("After SetByOut: " + number);  // number = 34

void AddOneByRef(ref int value)
{
    value++;
}

void SetByOut(out int value)
{
    value = 34;
}

Живая демонстрация на .NET скрипке

Ниже не компилируется, так как out параметров должен иметь значение , присвоенное до возврата метода (он будет компилировать с помощью ref , а):

void PrintByOut(out int value)
{
    Console.WriteLine("Hello!");
}

использование ключевого слова как Generic Modifier

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

Ковариация позволяет использовать более производный тип, чем тот, который задан общим параметром. Это позволяет неявное преобразование классов, которые реализуют варианты интерфейсов и неявное преобразование типов делегатов. Ковариантность и контравариантность поддерживаются для ссылочных типов, но они не поддерживаются для типов значений. - MSDN

//if we have an interface like this
interface ICovariant<out R> { }

//and two variables like
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();

// then the following statement is valid
// without the out keyword this would have thrown error
iobj = istr; // implicit conversion occurs here

checked, unchecked

checked и unchecked ключевые слова определяют, как операции обрабатывают математическое переполнение. «Переполнение» в контексте checked и unchecked ключевых слов - это когда целочисленная арифметическая операция приводит к значению, которое больше по величине, чем может представлять целевой тип данных.

Когда переполнение происходит в пределах checked блока (или когда компилятор настроен на глобальное использование проверенной арифметики), исключение выдается для предупреждения о нежелательном поведении. Между тем, в unchecked блоке, переполнение не работает: никаких исключений не выбрасывается, и значение просто переносится на противоположную границу. Это может привести к тонким, трудно найти ошибки.

Поскольку большинство арифметических операций выполняются по значениям, которые не являются большими или достаточно малыми для переполнения, большую часть времени нет необходимости явно определять блок как checked . Необходимо проявлять осторожность при выполнении арифметики на неограниченном входе, что может вызвать переполнение, например, при выполнении арифметики в рекурсивных функциях или при вводе пользователя.

Не checked и unchecked checked влияние арифметических операций с плавающей запятой.

Когда блок или выражение объявляются как unchecked , любые арифметические операции внутри него допускают переполнение без возникновения ошибки. Примером, когда это поведение является желательным , является вычисление контрольной суммы, когда значение разрешено «обертывать» во время вычисления:

byte Checksum(byte[] data) {
    byte result = 0;
    for (int i = 0; i < data.Length; i++) {
        result = unchecked(result + data[i]); // unchecked expression
    }
    return result;
}

Одним из наиболее распространенных применений для unchecked является реализация настраиваемого переопределения для object.GetHashCode() , типа контрольной суммы. Вы можете увидеть использование ключевого слова в ответах на этот вопрос: Каков наилучший алгоритм для переопределенного System.Object.GetHashCode? ,

Когда блок или выражение объявляются как checked , любая арифметическая операция, которая вызывает переполнение, приводит к тому, что генерируется OverflowException .

int SafeSum(int x, int y) {
    checked { // checked block
        return x + y; 
    }
}

Как проверенные, так и непроверенные могут быть в форме блока и выражения.

Проверенные и непроверенные блоки не влияют на вызываемые методы, только операторы, вызываемые непосредственно в текущем методе. Например, Enum.ToObject() , Convert.ToInt32() и определяемые пользователем операторы не зависят от настраиваемых проверенных / непроверенных контекстов.

Примечание . По умолчанию поведение по умолчанию переполнения (проверено или не отмечено) может быть изменено в свойствах проекта или с помощью переключателя командной строки [+ | -] . Обычно по умолчанию проверяются операции для отладочных сборников и неконтролируемые для выпуска сборок. checked и unchecked ключевые слова будут использоваться только тогда, когда подход по умолчанию не применяется, и для обеспечения правильности требуется четкое поведение.

идти к

goto может использоваться для перехода к определенной строке внутри кода, указанной меткой.

goto как:

Этикетка:

void InfiniteHello()
{
    sayHello:
    Console.WriteLine("Hello!");
    goto sayHello;
}

Живая демонстрация на .NET скрипке

Дело:

enum Permissions { Read, Write };

switch (GetRequestedPermission())
{
    case Permissions.Read:
        GrantReadAccess();
        break;

    case Permissions.Write:
        GrantWriteAccess();
        goto case Permissions.Read; //People with write access also get read
}

Живая демонстрация на .NET скрипке

Это особенно полезно при выполнении множественных действий в инструкции switch, поскольку C # не поддерживает распашные блоки case .

Исправление исключений

var exCount = 0;
retry:
try
{
    //Do work
}
catch (IOException)
{
    exCount++;
    if (exCount < 3)
    {
        Thread.Sleep(100);
        goto retry;
    }
    throw;
}

Живая демонстрация на .NET скрипке

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

Допустимые действия goto которые применяются к C #:

  • Провал в инструкции switch.

  • Многоуровневый разрыв. LINQ часто может использоваться вместо этого, но обычно он имеет худшую производительность.

  • Освобождение ресурсов при работе с развернутыми объектами низкого уровня. В C # объекты низкого уровня обычно должны быть обернуты отдельными классами.

  • Конечные автоматы, например парсеры; используемый внутренне компилятором, созданным машинами состояния async / wait.

перечисление

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

public enum DaysOfWeek
{
    Monday,
    Tuesday,
}

Вы можете указать конкретное значение для каждого из них (или некоторых из них):

public enum NotableYear
{
   EndOfWwI = 1918;
   EnfOfWwII = 1945,
}

В этом примере я опустил значение для 0, это, как правило, плохая практика. enum всегда будет иметь значение по умолчанию, выраженное явным преобразованием (YourEnumType) 0 , где YourEnumType - ваш объявленный тип enume . Без значения 0 определяется, что enum не будет иметь определенного значения при инициализации.

Основной тип enum умолчанию - int , вы можете изменить базовый тип на любой интегральный тип, включая byte , sbyte , short , ushort , int , uint , long и ulong . Ниже перечисление с byte базового типа:

enum Days : byte
{
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
};

Также обратите внимание, что вы можете конвертировать в / из базового типа просто с помощью броска:

int value = (int)NotableYear.EndOfWwI;

По этим причинам вам лучше всегда проверять правильность enum когда вы просматриваете библиотечные функции:

void PrintNotes(NotableYear year)
{
    if (!Enum.IsDefined(typeof(NotableYear), year))
        throw InvalidEnumArgumentException("year", (int)year, typeof(NotableYear));

    // ...
}

база

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

Выбор конструктора

public class Child : SomeBaseClass {
    public Child() : base("some string for the base class")
    {
    }
}

public class SomeBaseClass {
    public SomeBaseClass()
    {
        // new Child() will not call this constructor, as it does not have a parameter
    }
    public SomeBaseClass(string message)
    {
        // new Child() will use this base constructor because of the specified parameter in Child's constructor
        Console.WriteLine(message);
    }
}

Вызов базовой реализации виртуального метода

public override void SomeVirtualMethod() {
    // Do something, then call base implementation
    base.SomeVirtualMethod();
}

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

public class Parent
{
    public virtual int VirtualMethod()
    {
        return 1;
    }
}

public class Child : Parent
{
    public override int VirtualMethod() {
        return 11;
    }

    public int NormalMethod()
    {
        return base.VirtualMethod();
    }

    public void CallMethods()
    {
        Assert.AreEqual(11, VirtualMethod());

        Assert.AreEqual(1, NormalMethod());
        Assert.AreEqual(1, base.VirtualMethod());
    }
}

public class GrandChild : Child
{
    public override int VirtualMethod()
    {
        return 21;
    }

    public void CallAgain()
    {
        Assert.AreEqual(21, VirtualMethod());
        Assert.AreEqual(11, base.VirtualMethod());

        // Notice that the call to NormalMethod below still returns the value
        // from the extreme base class even though the method has been overridden
        // in the child class.
        Assert.AreEqual(1, NormalMethod());
    }
}

для каждого

foreach используется для итерации по элементам массива или элементам внутри коллекции, которая реализует IEnumerable ✝.

var lines = new string[] { 
    "Hello world!", 
    "How are you doing today?", 
    "Goodbye"
};

foreach (string line in lines)
{
    Console.WriteLine(line);
}

Это приведет к выводу

"Привет, мир!"
"Как у тебя сегодня дела?"
"Прощай"

Живая демонстрация на .NET скрипке

Вы можете выйти из цикла foreach в любой момент, используя ключевое слово break или перейти к следующей итерации с помощью ключевого слова continue .

var numbers = new int[] {1, 2, 3, 4, 5, 6};

foreach (var number in numbers)
{
    // Skip if 2
    if (number == 2)
        continue;

    // Stop iteration if 5
    if (number == 5)
        break;

    Console.Write(number + ", ");
}

// Prints: 1, 3, 4, 

Живая демонстрация на .NET скрипке

Обратите внимание, что порядок итераций гарантируется только для определенных коллекций, таких как массивы и List , но не гарантируется для многих других коллекций.


✝ Хотя IEnumerable обычно используется для указания перечислимых коллекций, foreach требует, чтобы сбор публично публиковал object GetEnumerator() , который должен возвращать объект, который предоставляет метод bool MoveNext() и object Current { get; } .

Титулы

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

static int AddAll(params int[] numbers)
{
    int total = 0;
    foreach (int number in numbers)
    {
        total += number;
    }
    
    return total;
}

Этот метод теперь можно вызывать с помощью типичного списка аргументов int или массива int.

AddAll(5, 10, 15, 20);                // 50
AddAll(new int[] { 5, 10, 15, 20 });  // 50

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


Будьте осторожны при перегрузке функций при использовании ключевого слова params . C # предпочитает сопоставлять более конкретные перегрузки, прежде чем прибегать к использованию перегрузок с params . Например, если у вас есть два метода:

static double Add(params double[] numbers)
{
    Console.WriteLine("Add with array of doubles");
    double total = 0.0;
    foreach (double number in numbers)
    {
        total += number;
    }
    
    return total;
}

static int Add(int a, int b)
{
    Console.WriteLine("Add with 2 ints");
    return a + b;
}

Тогда перед перегрузкой params приоритет будет иметь приоритет 2 аргументов.

Add(2, 3);      //prints "Add with 2 ints"
Add(2, 3.0);    //prints "Add with array of doubles" (doubles are not ints)
Add(2, 3, 4);   //prints "Add with array of doubles" (no 3 argument overload)

перерыв

В цикле (foreach, do, while, while) оператор break прерывает выполнение самого внутреннего цикла и возвращает его после кода. Также он может использоваться с yield в котором он указывает, что итератор подходит к концу.

for (var i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break;
    }
    Console.WriteLine("This will appear only 5 times, as the break will stop the loop.");
}

Живая демонстрация на .NET скрипке

foreach (var stuff in stuffCollection)
{
    if (stuff.SomeStringProp == null)
        break;
    // If stuff.SomeStringProp for any "stuff" is null, the loop is aborted.
    Console.WriteLine(stuff.SomeStringProp);
}

Оператор break также используется в конструкциях case-case для выхода из сегмента или сегмента по умолчанию.

switch(a)
{
    case 5:
        Console.WriteLine("a was 5!");
        break;

    default:
        Console.WriteLine("a was something else!");
        break;
}

В операторах switch ключевое слово break используется в конце каждого оператора case. Это противоречит некоторым языкам, которые позволяют «проваливаться» к следующему описанию случая в серии. Обходные пути для этого включают в себя операторы «goto» или стекирование операторов «case» последовательно.

Следующий код даст числа 0, 1, 2, ..., 9 и последняя строка не будет выполнена. yield break означает конец функции (а не только цикл).

public static IEnumerable<int> GetNumbers()
{
    int i = 0;
    while (true) {
        if (i < 10) {
            yield return i++;
        } else {
            yield break;
        }
    }
    Console.WriteLine("This line will not be executed");
}

Живая демонстрация на .NET скрипке

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

foreach (var outerItem in outerList)
{
    foreach (var innerItem in innerList)
    {
        if (innerItem.ShoudBreakForWhateverReason)
            // This will only break out of the inner loop, the outer will continue:
            break; 
    }
}

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

  • Оператор goto выпрыгивает из всей структуры цикла.
  • Специфическая переменная флага ( shouldBreak в следующем примере), которая может быть проверена в конце каждой итерации внешнего цикла.
  • Рефакторинг кода для использования оператора return в самом внутреннем цикле цикла или вообще полностью исключить всю структуру вложенных циклов.
bool shouldBreak = false;
while(comeCondition)
{
    while(otherCondition)
    {
        if (conditionToBreak)
        {
            // Either tranfer control flow to the label below...
            goto endAllLooping;

            // OR use a flag, which can be checked in the outer loop:
            shouldBreak = true;
        }
    }

    if(shouldBreakNow)
    {
        break; // Break out of outer loop if flag was set to true
    }
}

endAllLooping: // label from where control flow will continue

Аннотация

Класс , отмеченные ключевыми словами abstract не может быть создан.

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

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

abstract class Animal 
{
    string Name { get; set; }
    public abstract void MakeSound();
}

public class Cat : Animal 
{
    public override void MakeSound()
    {
        Console.WriteLine("Meov meov");
    }
}

public class Dog : Animal 
{   
    public override void MakeSound()
    {
        Console.WriteLine("Bark bark");
    }
}

Animal cat = new Cat();       // Allowed due to Cat deriving from Animal
cat.MakeSound();              // will print out "Meov meov"    

Animal dog = new Dog();       // Allowed due to Dog deriving from Animal
dog.MakeSound();              // will print out "Bark bark"

Animal animal = new Animal(); // Not allowed due to being an abstract class

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

abstract class Animal 
{
   public abstract string Name { get; set; }
}

public class Cat : Animal 
{
    public override string Name { get; set; }
}

public class Dog : Animal 
{
    public override string Name { get; set; }
}

float, double, decimal

поплавок

float - это псевдоним для типа данных .NET System.Single . Он позволяет хранить данные с плавающей точкой с одиночной точностью IEEE 754. Этот тип данных присутствует в mscorlib.dll который неявно ссылается на каждый проект C # при их создании.

Ориентировочный диапазон: от -3,4 × 10 38 до 3,4 × 10 38

Десятичная точность: 6-9 значащих цифр

Обозначение :

float f = 0.1259;
var f1 = 0.7895f; // f is literal suffix to represent float values 

Следует отметить, что тип float часто приводит к значительным ошибкам округления. В приложениях, где важна точность, следует учитывать другие типы данных.


двойной

double - это псевдоним для типа данных System.Double . Он представляет собой 64-битное число с плавающей запятой двойной точности. Этот тип данных присутствует в mscorlib.dll который неявно упоминается в любом проекте C #.

Диапазон: ± 5,0 × 10 -324 ± 1,7 × 10 308

Десятичная точность: 15-16 значащих цифр

Обозначение :

double distance = 200.34; // a double value
double salary = 245; // an integer implicitly type-casted to double value
var marks = 123.764D; // D is literal suffix to represent double values

десятичный

decimal - это псевдоним для типа данных .NET. System.Decimal . Он представляет ключевое слово, указывающее 128-битный тип данных. По сравнению с типами с плавающей запятой десятичный тип имеет более высокую точность и меньший диапазон, что делает его подходящим для финансовых и денежных расчетов. Этот тип данных присутствует в mscorlib.dll который неявно упоминается в любом проекте C #.

Диапазон: -7,9 × 10 28 до 7,9 × 10 28

Десятичная точность: 28-29 значащих цифр

Обозначение :

decimal payable = 152.25m; // a decimal value
var marks = 754.24m; // m is literal suffix to represent decimal values

UINT

Целое число без знака , или uint , представляет собой числовой тип данных, который может содержать только целые положительные числа. Как и в случае с названием, он представляет собой 32-битное целое число без знака. Ключевое слово uint является псевдонимом для типа Common Type System.UInt32 . Этот тип данных присутствует в mscorlib.dll , который неявно ссылается на каждый проект C # при их создании. Он занимает четыре байта памяти.

Целочисленные значения без знака могут содержать любое значение от 0 до 4 294 967 295.

Примеры того, как и теперь не объявлять целые числа без знака

uint i = 425697; // Valid expression, explicitly stated to compiler
var i1 = 789247U; // Valid expression, suffix allows compiler to determine datatype
uint x = 3.0; // Error, there is no implicit conversion

Обратите внимание: согласно Microsoft рекомендуется использовать тип данных int, где это возможно, поскольку тип данных uint не соответствует CLS.

этот

this ключевое слово относится к текущему экземпляру класса (объект). Таким образом, можно различать две переменные с одним и тем же именем, один на уровне класса (поле) и один, являющийся параметром (или локальной переменной) метода.

public MyClass {
    int a;

    void set_a(int a)
    {
        //this.a refers to the variable defined outside of the method,
        //while a refers to the passed parameter.
        this.a = a;
    }
}

Другими способами использования ключевого слова являются цепочки нестатических перегрузок конструктора :

public MyClass(int arg) : this(arg, null)
{
}

и написание индексаторов :

public string this[int idx1, string idx2]
{
    get { /* ... */ }
    set { /* ... */ }
}

и объявление методов расширения :

public static int Count<TItem>(this IEnumerable<TItem> source)
{
    // ...
}

Если нет конфликта с локальной переменной или параметром, то вопрос в том, следует ли использовать this или нет, так это this.MemberOfType и MemberOfType были бы эквивалентны в этом случае. Также см. Ключевое слово base .

Обратите внимание, что если в текущем экземпляре должен быть вызван метод расширения, this необходимо. Например , если ваш внутри не-статический метод класса , который реализует IEnumerable<> , и вы хотите , чтобы вызвать расширение Count из ранее, вы должны использовать:

this.Count()  // works like StaticClassForExtensionMethod.Count(this)

и this не может быть опущено там.

за

Синтаксис: for (initializer; condition; iterator)

  • Цикл for обычно используется, когда число итераций известно.
  • Операторы в секции initializer выполняются только один раз, прежде чем вводить цикл.
  • Раздел « condition » содержит логическое выражение, которое оценивается в конце каждой итерации цикла, чтобы определить, должен ли цикл выйти или должен работать снова.
  • Секция iterator определяет, что происходит после каждой итерации тела цикла.

Этот пример показывает , как for можно использовать для перебора символов строки:

string str = "Hello";
for (int i = 0; i < str.Length; i++)
{
    Console.WriteLine(str[i]);                
}

Выход:

ЧАС
е
L
L
о

Живая демонстрация на .NET скрипке

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

for( ; ; )
{
    // Your code here
}

Секция initializer может содержать несколько переменных, если они одного типа. Раздел condition может состоять из любого выражения, которое может быть оценено в bool . И секция iterator может выполнять несколько действий, разделенных запятой:

string hello = "hello";
for (int i = 0, j = 1, k = 9; i < 3 && k > 0; i++, hello += i) {
    Console.WriteLine(hello);
}

Выход:

Привет
hello1
hello12

Живая демонстрация на .NET скрипке

в то время как

Оператор while выполняет итерацию над блоком кода до тех пор, пока условный запрос не будет равен false или код будет прерван goto , return , break или throw .

Синтаксис для ключевого слова while :

while ( condition ) { code block; }

Пример:

int i = 0;
while (i++ < 5)
{
    Console.WriteLine("While is on loop number {0}.", i);
}

Выход:

«Пока находится цикл №1».
«Пока находится петля номер 2.»
«Пока находится петля номер 3.»
«Пока находится петля номер 4.»
«Пока находится петля номер 5.»

Живая демонстрация на .NET скрипке

Цикл while - Entry Controlled , поскольку условие проверяется перед выполнением заключенного кодового блока. Это означает, что цикл while не будет выполнять свои инструкции, если условие ложно.

bool a = false;

while (a == true)
{
    Console.WriteLine("This will never be printed.");
}

Давать while состояние без инициализации, чтобы он стал ложными в каком - то момент приведет к бесконечному или бесконечному циклу. Насколько это возможно, этого следует избегать, однако, если вам это нужно, могут возникнуть некоторые исключительные обстоятельства.

Вы можете создать такой цикл следующим образом:

while (true)
{
//...
}

Обратите внимание, что компилятор C # будет преобразовывать циклы, такие как

while (true)
{
// ...
}

или же

for(;;)
{
// ...
}

в

{
:label
// ...
goto label;
}

Обратите внимание, что цикл while может иметь любое условие, независимо от того, насколько он сложный, если он оценивает (или возвращает) логическое значение (bool). Он также может содержать функцию, которая возвращает логическое значение (поскольку такая функция оценивается с тем же типом, что и выражение, такое как `a == x '). Например,

while (AgriculturalService.MoreCornToPick(myFarm.GetAddress()))
{
    myFarm.PickCorn();
}

вернуть

MSDN: оператор return завершает выполнение метода, в котором он появляется, и возвращает управление вызывающему методу. Он также может вернуть необязательное значение. Если метод является типом void, оператор return может быть опущен.

public int Sum(int valueA, int valueB)
{
    return valueA + valueB;
}


public void Terminate(bool terminateEarly)
{
    if (terminateEarly) return; // method returns to caller if true was passed in
    else Console.WriteLine("Not early"); // prints only if terminateEarly was false
}

в

Ключевое слово in умолчанию имеет три варианта использования:

a) Как часть синтаксиса в инструкции foreach или как часть синтаксиса в запросе LINQ

foreach (var member in sequence)
{
    // ...
}

б) В контексте общих интерфейсов и общих типов делегатов означает контравариантность для рассматриваемого параметра типа:

public interface IComparer<in T>
{
    // ...
}

c) В контексте запроса LINQ относится к коллекции, которая запрашивается

var query = from x in source select new { x.Name, x.ID, };

с помощью

Существует два типа using ключевых слов, using statement и using directive :

  1. используя инструкцию :

    Ключевое слово using гарантирует, что объекты, реализующие интерфейс IDisposable будут правильно удалены после использования. Существует отдельная тема для оператора using

  2. с использованием директивы

    Директива using имеет три варианта использования, см. Страницу msdn для директивы use . Существует отдельная тема для директивы use .

запечатанный

При применении к классу sealed модификатор предотвращает наследование других классов.

class A { }
sealed class B : A { }
class C : B { } //error : Cannot derive from the sealed class

При применении к virtual методу (или виртуальному свойству) sealed модификатор предотвращает переопределение этого метода (свойства) в производных классах.

public class A 
{
    public sealed override string ToString() // Virtual method inherited from class Object
    {
        return "Do not override me!";
    }
}

public class B: A 
{
    public override string ToString() // Compile time error
    { 
        return "An attempt to override"; 
    }
}

размер

Используется для получения размера в байтах для неуправляемого типа

int byteSize = sizeof(byte) // 1
int sbyteSize = sizeof(sbyte) // 1
int shortSize = sizeof(short) // 2
int ushortSize = sizeof(ushort) // 2
int intSize = sizeof(int) // 4
int uintSize = sizeof(uint) // 4
int longSize = sizeof(long) // 8
int ulongSize = sizeof(ulong) // 8
int charSize = sizeof(char) // 2(Unicode)
int floatSize = sizeof(float) // 4
int doubleSize = sizeof(double) // 8
int decimalSize = sizeof(decimal) // 16
int boolSize = sizeof(bool) // 1

статический

static модификатор используется для объявления статического члена, который не нужно создавать, чтобы получить доступ, но вместо этого открывается просто через его имя, то есть DateTime.Now .

static может использоваться с классами, полями, методами, свойствами, операторами, событиями и конструкторами.

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

class A
{
    static public int count = 0;

    public A()
    {
        count++;
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        A b = new A();
        A c = new A();

        Console.WriteLine(A.count); // 3 
    }
}

count равно общему числу экземпляров класса A

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

class A
{
    static public DateTime InitializationTime;

    // Static constructor
    static A()
    {
        InitializationTime = DateTime.Now;
        // Guaranteed to only run once
        Console.WriteLine(InitializationTime.ToString());
    }
}

static class помечен static ключевым словом и может использоваться в качестве полезного контейнера для набора методов, которые работают с параметрами, но необязательно требуют привязки к экземпляру. Из-за static природы класса он не может быть создан, но может содержать static constructor . Некоторые функции static class включают:

  • Не может быть унаследован
  • Не может наследовать ничего, кроме Object
  • Может содержать статический конструктор, но не конструктор экземпляра
  • Может содержать только статические элементы
  • Запечатан

Компилятор также дружелюбен и позволит разработчику узнать, существуют ли какие-либо члены экземпляра в классе. Примером может служить статический класс, который преобразует между метриками США и Канады:

static class ConversionHelper {
    private static double oneGallonPerLitreRate = 0.264172;

    public static double litreToGallonConversion(int litres) {
        return litres * oneGallonPerLitreRate;
    }
}

Когда классы объявляются static:

public static class Functions
{
  public static int Double(int value)
  {
    return value + value;
  }
}

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

Поскольку C # 6 static также можно использовать вместе using импортом статических элементов и методов. Они могут использоваться без имени класса.

Старый способ, без using static :

using System;

public class ConsoleApplication
{
    public static void Main()
    {
         Console.WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Пример using static

using static System.Console;

public class ConsoleApplication
{
    public static void Main()
    {
         WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Недостатки

Хотя статические классы могут быть невероятно полезными, у них есть свои собственные оговорки:

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

  • Статический класс не может реализовать интерфейс.

ИНТ

int - это псевдоним для System.Int32 , который является типом данных для подписанных 32-битных целых чисел. Этот тип данных можно найти в mscorlib.dll который неявно ссылается на каждый проект C # при их создании.

Диапазон: -2,147,483,648 до 2,147,483,647

int int1 = -10007;
var int2 = 2132012521;     

долго

Ключевое слово long используется для представления подписанных 64-битных целых чисел. Это псевдоним для типа System.Int64 присутствующего в mscorlib.dll , который неявно ссылается на каждый проект C # при их создании.

Любая длинная переменная может быть объявлена ​​как явно, так и неявно:

long long1 = 9223372036854775806;  // explicit declaration, long keyword used
var long2 = -9223372036854775806L; // implicit declaration, 'L' suffix used

Длинная переменная может содержать любое значение от -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807 и может быть полезна в ситуациях, когда переменная должна содержать значение, превышающее границы других переменных (таких как переменная int ).

ULONG

Ключевое слово, используемое для 64-битных целых чисел без знака. Он представляет тип данных System.UInt64 найденный в mscorlib.dll который неявно ссылается на каждый проект C # при их создании.

Диапазон: от 0 до 18 446 744 073 709 551 615

ulong veryLargeInt = 18446744073609451315;
var anotherVeryLargeInt = 15446744063609451315UL;

динамический

dynamic ключевое слово используется с динамически типизированными объектами . Объекты, объявленные как dynamic статические проверки перед компиляцией, и затем оцениваются во время выполнения.

using System;
using System.Dynamic;

dynamic info = new ExpandoObject();
info.Id = 123;
info.Another = 456;

Console.WriteLine(info.Another);
// 456

Console.WriteLine(info.DoesntExist);
// Throws RuntimeBinderException

Следующий пример использует dynamic с библиотекой Newtonsoft Json.NET, чтобы легко считывать данные из десериализованного файла JSON.

try
{
    string json = @"{ x : 10, y : ""ho""}";
    dynamic deserializedJson = JsonConvert.DeserializeObject(json);
    int x = deserializedJson.x;
    string y = deserializedJson.y;
    // int z = deserializedJson.z; // throws RuntimeBinderException
}
catch (RuntimeBinderException e)
{
    // This exception is thrown when a property
    // that wasn't assigned to a dynamic variable is used
}

Существуют некоторые ограничения, связанные с ключевым словом dynamic. Одним из них является использование методов расширения. В следующем примере добавлен метод расширения для строки: SayHello .

static class StringExtensions
{
    public static string SayHello(this string s) => $"Hello {s}!";
}

Первый подход будет состоять в том, чтобы назвать его как обычно (как для строки):

var person = "Person";
Console.WriteLine(person.SayHello());

dynamic manager = "Manager";
Console.WriteLine(manager.SayHello()); // RuntimeBinderException

Ошибка компиляции, но во время выполнения вы получаете RuntimeBinderException . Обходным путем для этого будет вызов метода расширения через статический класс:

var helloManager = StringExtensions.SayHello(manager);
Console.WriteLine(helloManager);

виртуальный, переопределить, новый

виртуальный и переопределить

Ключевое слово virtual позволяет переопределять метод, свойство, индексор или событие с помощью производных классов и текущего полиморфного поведения. (Члены по умолчанию не являются виртуальными в C #)

public class BaseClass
{
    public virtual void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

Для переопределения члена ключевое слово override используется в производных классах. (Обратите внимание, что подпись членов должна быть одинаковой)

public class DerivedClass: BaseClass
{
    public override void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

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

Короче говоря, объект может быть объявлен типа BaseClass во время компиляции, но если во время выполнения это экземпляр DerivedClass тогда будет выполнен переопределенный элемент:

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

Переопределение метода необязательно:

public class SecondDerivedClass: DerivedClass {}

var obj1 = new SecondDerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

новый

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

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too!    

Когда это происходит, выполняемый элемент всегда определяется во время компиляции в зависимости от типа объекта.

  • Если объект объявлен типа BaseClass (даже если во время выполнения имеет производный класс), то выполняется метод BaseClass
  • Если объект объявлен типа DerivedClass тогда выполняется метод DerivedClass .

Обычно это случайность (когда член добавляется к базовому типу после того, как идентичный был добавлен к производному типу), и в этих сценариях генерируется предупреждение CS0108 компилятора.

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

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public new void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too! 

Использование переопределения не является необязательным

В отличие от C ++, использование ключевого слова override не является необязательным:

public class A
{
    public virtual void Foo()
    {
    }
}

public class B : A
{
    public void Foo() // Generates CS0108
    {
    }
}

Приведенный выше пример также вызывает предупреждение CS0108 , потому что B.Foo() не автоматически переопределяет A.Foo() . Добавить override когда намерение переопределить базовый класс и вызвать полиморфное поведение, добавить new когда вы хотите неполиморфное поведение и разрешить вызов с использованием статического типа. Последнее следует использовать с осторожностью, так как это может вызвать серьезную путаницу.

Следующий код даже приводит к ошибке:

public class A
{
    public void Foo()
    {
    }
}

public class B : A
{
    public override void Foo() // Error: Nothing to override
    {
    }
}

Производные классы могут вводить полиморфизм

Следующий код является абсолютно допустимым (хотя и редким):

    public class A
    {
        public void Foo()
        {
            Console.WriteLine("A");
        }
    }

    public class B : A
    {
        public new virtual void Foo() 
        {
            Console.WriteLine("B");
        }
    }

Теперь все объекты со статической ссылкой B (и ее производных) используют полиморфизм для разрешения Foo() , а ссылки A используют A.Foo() .

A a = new A();
a.Foo(); // Prints "A";
a = new B();
a.Foo(); // Prints "A";
B b = new B();
b.Foo(); // Prints "B";

Виртуальные методы не могут быть частными

Компилятор C # строг в предотвращении бессмысленных конструкций. Методы, помеченные как virtual не могут быть частными. Поскольку частный метод не может быть замечен из производного типа, он не может быть перезаписан. Это не скомпилируется:

public class A
{
    private virtual void Foo() // Error: virtual methods cannot be private
    {
    }
}

асинхронный, ждут

Ключевое слово await было добавлено как часть выпуска C # 5.0, которое поддерживается в Visual Studio 2012 и далее. Он использует параллельную библиотеку задач (TPL), которая упрощает многопоточность. async и await ключевые слова используются в паре в той же функции, что показано ниже. Ключевое слово await используется для приостановки выполнения текущего асинхронного метода до тех пор, пока ожидаемая асинхронная задача не будет завершена и / или ее результаты не будут возвращены. Чтобы использовать ключевое слово await , метод, который его использует, должен быть помечен как ключевое слово async .

Использование async с void сильно обескуражено. Для получения дополнительной информации вы можете посмотреть здесь .

Пример:

public async Task DoSomethingAsync()
{    
    Console.WriteLine("Starting a useless process...");
    Stopwatch stopwatch = Stopwatch.StartNew();
    int delay = await UselessProcessAsync(1000);
    stopwatch.Stop();
    Console.WriteLine("A useless process took {0} milliseconds to execute.", stopwatch.ElapsedMilliseconds);
}

public async Task<int> UselessProcessAsync(int x)
{
    await Task.Delay(x);
    return x;
}

Выход:

«Начать бесполезный процесс ...»

** ... 1 секунда задержка ... **

«Бесполезный процесс занял 1000 миллисекунд».

async ключевых слов async и await могут быть опущены, если метод возврата Task или Task<T> возвращает только одну асинхронную операцию.

Вместо этого:

public async Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    await Task.Delay(x);
}

Это предпочтительнее:

public Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    return Task.Delay(x);
}
5.0

В C # 5.0 await не может быть использовано в catch и, finally .

6,0

С C # 6.0 await может быть использованы в catch и в finally .

голец

Символ - это одна буква, хранящаяся внутри переменной. Это встроенный тип значения, который занимает два байта памяти. Он представляет собой тип данных System.Char найденный в mscorlib.dll который неявно ссылается на каждый проект C # при их создании.

Существует несколько способов сделать это.

  1. char c = 'c';
  2. char c = '\u0063'; //Unicode
  3. char c = '\x0063'; //Hex
  4. char c = (char)99;//Integral

ushort, int, uint, long, ulong, float, double, может быть неявно преобразован в ushort, int, uint, long, ulong, float, double, или decimal и он вернет целочисленное значение этого символа.

ushort u = c;

возвращает 99 и т.д.

Однако нет никаких неявных преобразований из других типов в char. Вместо этого вы должны бросить их.

ushort u = 99;
 char c = (char)u;

замок

lock обеспечивает безопасность потока для блока кода, так что к нему можно получить доступ только одним потоком в рамках одного и того же процесса. Пример:

private static object _lockObj = new object();
static void Main(string[] args)
{
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());

    Console.ReadKey();
}

private static void TaskWork()
{
    lock(_lockObj)
    {
        Console.WriteLine("Entered");

        Task.Delay(3000);
        Console.WriteLine("Done Delaying");

        // Access shared resources safely

        Console.WriteLine("Leaving");
    }   
}

Output:

Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving

Случаи применения:

Всякий раз, когда у вас есть блок кода, который может создавать побочные эффекты, если он выполняется несколькими потоками одновременно. Ключевое слово блокировки вместе с общим объектом синхронизации ( _objLock в примере) можно использовать для предотвращения этого.

Обратите внимание, что _objLock не может быть null а несколько потоков, выполняющих код, должны использовать один и тот же экземпляр объекта (либо путем static поля, либо с помощью одного и того же экземпляра класса для обоих потоков)

Со стороны компилятора ключевое слово блокировки является синтаксическим сахаром, который заменяется на Monitor.Enter(_lockObj); и Monitor.Exit(_lockObj); , Поэтому, если вы замените блокировку, окружив блок кода этими двумя методами, вы получите те же результаты. Вы можете увидеть фактический код в синтаксическом сахаре в примере C # - lock

ноль

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

null - это ключевое слово, которое представляет собой нулевую ссылку.

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

object a = null;
string b = null;
int? c = null;
List<int> d  = null;

Недействительным типам значений не может быть присвоена нулевая ссылка. Все следующие присвоения недействительны:

int a = null; 
float b = null;
decimal c = null;

Нулевую ссылку не следует путать с действительными экземплярами различных типов, таких как:

  • пустой список ( new List<int>() )
  • пустая строка ( "" )
  • число 0 ( 0 , 0f , 0m )
  • нулевой символ ( '\0' )

Иногда имеет смысл проверить, является ли что-либо нулевым или пустым / стандартным объектом. Для проверки этого может использоваться метод System.String.IsNullOrEmpty (String), или вы можете реализовать свой собственный эквивалентный метод.

private void GreetUser(string userName)
{
    if (String.IsNullOrEmpty(userName))
    {
        //The method that called us either sent in an empty string, or they sent us a null reference. Either way, we need to report the problem.
        throw new InvalidOperationException("userName may not be null or empty.");
    }
    else
    {
        //userName is acceptable.
        Console.WriteLine("Hello, " + userName + "!");
    }
}

внутренний

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

использование:

public class BaseClass 
{
    // Only accessible within the same assembly
    internal static int x = 0;
}

Разница между различными модификаторами доступа уточняется здесь.

Модификаторы доступа

общественности

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

частный

Доступ к типу или члену может получить только код в том же классе или структуре.

защищенный

Доступ к типу или члену может получить только код в том же классе или структуре или в производном классе.

внутренний

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

защищенный внутренний

Доступ к типу или члену может получить любой код в той же сборке или любой производный класс в другой сборке.

Когда не установлен модификатор доступа , используется модификатор доступа по умолчанию. Таким образом, всегда есть модификатор доступа, даже если он не установлен.

где

where может служить две цели в C #: ограничение типа в общем аргументе и фильтрация запросов LINQ.

В обобщенном классе давайте рассмотрим

public class Cup<T>
{
    // ...
}

T называется параметром типа. Определение класса может налагать ограничения на фактические типы, которые могут быть предоставлены для T.

Могут применяться следующие виды ограничений:

  • тип ценности
  • ссылочный тип
  • конструктор по умолчанию
  • наследование и реализация

тип ценности

В этом случае может быть предоставлена ​​только struct s (сюда входят «примитивные» типы данных, такие как int , boolean т. Д.)

public class Cup<T> where T : struct
{
    // ...
}

ссылочный тип

В этом случае могут быть предоставлены только типы классов

public class Cup<T> where T : class
{
    // ...
}

гибридное значение / ссылочный тип

Иногда требуется ограничить аргументы типа доступными в базе данных, и они обычно будут отображаться для типов значений и строк. Поскольку все ограничения типов должны быть выполнены, невозможно указать, where T : struct or string (это недопустимый синтаксис). Обходной путь заключается в ограничении аргументов типа IConvertible который имеет встроенные типы «... Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char и String. " Возможно, другие объекты будут реализовывать IConvertible, хотя на практике это редко.

public class Cup<T> where T : IConvertible
{
    // ...
}

конструктор по умолчанию

Разрешены только типы, содержащие конструктор по умолчанию. Это включает типы значений и классы, которые содержат конструктор по умолчанию (без параметров)

public class Cup<T> where T : new
{
    // ...
}

наследование и реализация

Могут поставляться только типы, которые наследуются от определенного базового класса или реализуют определенный интерфейс.

public class Cup<T> where T : Beverage
{
    // ...
}


public class Cup<T> where T : IBeer
{
    // ...
}

Ограничение может даже ссылаться на другой параметр типа:

public class Cup<T, U> where U : T
{
    // ...
}

Для аргумента типа можно указать несколько ограничений:

public class Cup<T> where T : class, new()
{
    // ...
}

В предыдущих примерах показаны общие ограничения для определения класса, но ограничения могут использоваться везде, где предоставляется аргумент типа: классы, структуры, интерфейсы, методы и т. Д.

where также может быть предложение LINQ. В этом случае он аналогичен WHERE в SQL:

int[] nums = { 5, 2, 1, 3, 9, 8, 6, 7, 2, 0 };

var query =
    from num in nums 
    where num < 5
    select num;

    foreach (var n in query)
    {
        Console.Write(n + " ");
    }
    // prints 2 1 3 2 0

внешний

Ключевое слово extern используется для объявления методов, которые реализуются извне. Это можно использовать вместе с атрибутом DllImport для вызова неуправляемого кода с использованием служб Interop. который в этом случае будет иметь static модификатор

Например:

using System.Runtime.InteropServices;
public class MyClass
{
    [DllImport("User32.dll")]
    private static extern int SetForegroundWindow(IntPtr point);

    public void ActivateProcessWindow(Process p)
    {
        SetForegroundWindow(p.MainWindowHandle);
    }
}

Это использует метод SetForegroundWindow, импортированный из библиотеки User32.dll

Это также можно использовать для определения внешнего ассемблера сборки. которые позволяют нам ссылаться на разные версии одних и тех же компонентов из одной сборки.

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

/r:GridV1=grid.dll
/r:GridV2=grid20.dll

Это создает внешние псевдонимы GridV1 и GridV2. Чтобы использовать эти псевдонимы из программы, обратитесь к ним с помощью ключевого слова extern. Например:

extern alias GridV1;
extern alias GridV2;

BOOL

Ключевое слово для хранения логических значений true и false . bool - это псевдоним System.Boolean.

Значение по умолчанию bool равно false.

bool b; // default value is false
b = true; // true
b = ((5 + 2) == 6); // false

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

Значение по умолчанию для bool? нулевой.

bool? a // default value is null

когда

when ключевое слово добавлено в C # 6 , оно используется для фильтрации исключений.

До появления ключевого слова when вас могло быть одно предложение catch для каждого типа исключения; с добавлением ключевого слова теперь возможен более мелкозернистый контроль.

A, when выражение присоединяется к ветви catch , и только если условие when равно true , будет выполняться предложение catch . Можно иметь несколько предложений catch с одинаковыми типами классов исключений и разными, when условия.

private void CatchException(Action action)
{
    try
    {
        action.Invoke();
    }
    
    // exception filter
    catch (Exception ex) when (ex.Message.Contains("when"))
    {
        Console.WriteLine("Caught an exception with when");
    }

    catch (Exception ex)
    {
        Console.WriteLine("Caught an exception without when");
    }
}

private void Method1() { throw new Exception("message for exception with when"); }
private void Method2() { throw new Exception("message for general exception"); }


CatchException(Method1);
CatchException(Method2);

непроверенный

unchecked ключевое слово запрещает компилятору проверять наличие переполнений / недочетов.

Например:

const int ConstantMax = int.MaxValue;
unchecked
{
    int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);

Без ключевого слова unchecked ни одна из двух операций добавления не будет скомпилирована.

Когда это полезно?

Это полезно, так как это может ускорить вычисления, которые, безусловно, не будут переполняться, поскольку проверка переполнения требует времени или когда требуется переполнение / недополнение (например, при генерации хэш-кода).

недействительным

Зарезервированное слово "void" является псевдонимом типа System.Void и имеет два применения:

  1. Объявить метод, который не имеет возвращаемого значения:
public void DoSomething()
{
    // Do some work, don't return any value to the caller.
}

Метод с возвращаемым типом void все равно может иметь ключевое слово return в своем теле. Это полезно, когда вы хотите выйти из выполнения метода и вернуть поток вызывающему:

public void DoSomething()
{
    // Do some work...

    if (condition)
        return;

    // Do some more work if the condition evaluated to false.
}
  1. Объявите указатель на неизвестный тип в небезопасном контексте.

В небезопасном контексте тип может быть типом указателя, типом значения или ссылочным типом. Объявление типа указателя обычно является type* identifier , где тип является известным типом, то есть int* myInt , но также может быть void* identifier , где тип неизвестен.

Обратите внимание, что объявление типа указателя void не рекомендуется Microsoft.

if, if ... else, if ... else if


Оператор if используется для управления потоком программы. Оператор if определяет, какой оператор запускаться на основе значения Boolean выражения.

Для одного оператора braces {} являются необязательными, но рекомендуется.

int a = 4;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
// output: "a contains an even number"

if также может иметь предложение else , которое будет выполнено в случае, если условие принимает значение false:

int a = 5;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number"

Конструкция if ... else if позволяет задать несколько условий:

int a = 9;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else if(a % 3 == 0) 
{
     Console.WriteLine("a contains an odd number that is a multiple of 3"); 
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number that is a multiple of 3"

Важно отметить, что если в приведенном выше примере выполняется условие, элемент управления пропускает другие тесты и переходит к концу этой конкретной, если else конструирует. Таким образом, порядок тестов важен, если вы используете if .. else if construct

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

if (someBooleanMethodWithSideEffects() && someOtherBooleanMethodWithSideEffects()) {
  //...
}

Нет гарантии, что someOtherBooleanMethodWithSideEffects будут работать.

Это также важно в тех случаях, когда более ранние условия гарантируют, что он «безопасен» для оценки более поздних. Например:

if (someCollection != null && someCollection.Count > 0) {
   // ..
}

Порядок очень важен в этом случае, потому что, если мы отменим порядок:

if (someCollection.Count > 0 && someCollection != null) {

он выкинет NullReferenceException если someCollection равен null .

делать

Оператор do выполняет итерацию по блоку кода до тех пор, пока условный запрос не будет равен false. Цикл do-while также может быть прерван goto , return , break или throw .

Синтаксис ключевого слова do :

do { code block; } while ( условие );

Пример:

int i = 0;

do
{
    Console.WriteLine("Do is on loop number {0}.", i);
} while (i++ < 5);

Выход:

«Do is on loop number 1.»
«Do is on loop number 2.»
«Do on on loop number 3.»
«Do is on loop number 4.»
«Do is on loop number 5.»

В отличие от цикла while цикл do-while является Exit Controlled . Это означает, что цикл do-while будет выполнять свои инструкции хотя бы один раз, даже если условие не выполняется в первый раз.

bool a = false;

do
{
    Console.WriteLine("This will be printed once, even if a is false.");
} while (a == true);

оператор

Большинство встроенных операторов (включая операторы преобразования) могут быть перегружены с помощью ключевого слова operator вместе с public и static модификаторами.

Операторы представлены в трех формах: унарные операторы, двоичные операторы и операторы преобразования.

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

Операторы преобразования должны конвертироваться в закрытый тип или из него.

public struct Vector32
{
    
    public Vector32(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public int X { get; }
    public int Y { get; }

    public static bool operator ==(Vector32 left, Vector32 right)
        => left.X == right.X && left.Y == right.Y;

    public static bool operator !=(Vector32 left, Vector32 right)
        => !(left == right);

    public static Vector32 operator +(Vector32 left, Vector32 right)
        => new Vector32(left.X + right.X, left.Y + right.Y);

    public static Vector32 operator +(Vector32 left, int right)
        => new Vector32(left.X + right, left.Y + right);

    public static Vector32 operator +(int left, Vector32 right)
        => right + left;

    public static Vector32 operator -(Vector32 left, Vector32 right)
        => new Vector32(left.X - right.X, left.Y - right.Y);

    public static Vector32 operator -(Vector32 left, int right)
        => new Vector32(left.X - right, left.Y - right);

    public static Vector32 operator -(int left, Vector32 right)
        => right - left;

    public static implicit operator Vector64(Vector32 vector)
        => new Vector64(vector.X, vector.Y);

    public override string ToString() => $"{{{X}, {Y}}}";

}

public struct Vector64
{

    public Vector64(long x, long y)
    {
        X = x;
        Y = y;
    }

    public long X { get; }
    public long Y { get; }

    public override string ToString() => $"{{{X}, {Y}}}";

}

пример

var vector1 = new Vector32(15, 39);
var vector2 = new Vector32(87, 64);
        
Console.WriteLine(vector1 == vector2); // false
Console.WriteLine(vector1 != vector2); // true
Console.WriteLine(vector1 + vector2);  // {102, 103}
Console.WriteLine(vector1 - vector2);  // {-72, -25}

структура

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

Классы - это ссылочные типы, structs - типы значений.

using static System.Console;

namespace ConsoleApplication1
{
    struct Point
    {
        public int X;
        public int Y;

        public override string ToString()
        {
            return $"X = {X}, Y = {Y}";
        }

        public void Display(string name)
        {
            WriteLine(name + ": " + ToString());
        }
    }

    class Program
    {
        static void Main()
        {
            var point1 = new Point {X = 10, Y = 20};
            // it's not a reference but value type
            var point2 = point1;
            point2.X = 777;
            point2.Y = 888;
            point1.Display(nameof(point1)); // point1: X = 10, Y = 20
            point2.Display(nameof(point2)); // point2: X = 777, Y = 888

            ReadKey();
        }
    }
}

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


Некоторые предложения от MS о том, когда использовать struct и когда использовать класс:

РАССМАТРИВАТЬ

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

ИЗБЕЖАТЬ

определяя структуру, если тип имеет все следующие характеристики:

  • Он логически представляет одно значение, подобное примитивным типам (int, double и т. Д.),
  • Он имеет размер экземпляра до 16 байт.
  • Это неизменно.
  • Его не нужно часто вставлять в бокс.

переключатель

Оператор switch - это оператор управления, который выбирает раздел переключателя для выполнения из списка кандидатов. Оператор switch включает в себя один или несколько разделов коммутатора. Каждая секция переключателя содержит один или несколько case меток следуют один или более операторов. Если ни одна метка случая не содержит соответствующего значения, управление передается в раздел по default , если таковой имеется. Случайное падение не поддерживается в C #, строго говоря. Однако, если 1 или более ярлыков case пусты, выполнение будет следовать за кодом следующего блока case который содержит код. Это позволяет группировку из нескольких case наклеек с одной и той же реализацией. В следующем примере, если month равен 12, код в case 2 будет выполнен , так как case этикеткой 12 1 и 2 сгруппированы. Если case блок не пуст, то break должен присутствовать до следующего case наклейки, в противном случае компилятор будет флаг ошибка.

int month = DateTime.Now.Month; // this is expected to be 1-12 for Jan-Dec

switch (month)
{
    case 12: 
    case 1: 
    case 2:
        Console.WriteLine("Winter");
        break;
    case 3: 
    case 4: 
    case 5:
        Console.WriteLine("Spring");
        break;
    case 6: 
    case 7: 
    case 8:
        Console.WriteLine("Summer");
        break;
    case 9:     
    case 10: 
    case 11:
        Console.WriteLine("Autumn");
        break;
    default:
        Console.WriteLine("Incorrect month index");
        break;
}

case может быть помечен только значением, известным во время компиляции (например, 1 , "str" , Enum.A ), поэтому variable не является допустимой меткой case , но значение const или Enum (а также любое буквальное значение).

интерфейс

interface содержит подписи методов, свойств и событий. Производные классы определяют члены, поскольку интерфейс содержит только объявление членов.

Интерфейс объявляется с использованием ключевого слова interface .

interface IProduct
{
    decimal Price { get; }
}

class Product : IProduct
{
    const decimal vat = 0.2M;
    
    public Product(decimal price)
    {
        _price = price;
    }
    
    private decimal _price;
    public decimal Price { get { return _price * (1 + vat); } }
}

небезопасный

unsafe ключевое слово может использоваться в объявлениях типов или методов или для объявления встроенного блока.

Цель этого ключевого слова - включить использование небезопасного подмножества C # для рассматриваемого блока. Небезопасное подмножество включает в себя такие функции, как указатели, распределение стека, массивы типа C и т. Д.

Небезопасный код не поддается проверке, поэтому его использование не рекомендуется. Компиляция небезопасного кода требует передачи коммутатора на компилятор C #. Кроме того, CLR требует, чтобы работающая сборка имела полное доверие.

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

В качестве очень простого примера

// compile with /unsafe
class UnsafeTest
{
   unsafe static void SquarePtrParam(int* p)
   {
      *p *= *p; // the '*' dereferences the pointer.
      //Since we passed in "the address of i", this becomes "i *= i"
   }

   unsafe static void Main()
   {
      int i = 5;
      // Unsafe method: uses address-of operator (&):
      SquarePtrParam(&i); // "&i" means "the address of i". The behavior is similar to "ref i"
      Console.WriteLine(i); // Output: 25
   }
}

При работе с указателями мы можем напрямую изменять значения ячеек памяти, а не обращаться к ним по имени. Обратите внимание, что для этого часто требуется использование ключевого слова fixed, чтобы предотвратить возможное повреждение памяти, поскольку сборщик мусора перемещает вещи (в противном случае вы можете получить ошибку CS0212 ). Поскольку переменную, которая была «фиксированной», не может быть записана, нам также часто приходится иметь второй указатель, который начинается с того же места, что и первый.

void Main()
{
    int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    UnsafeSquareArray(intArray);
    foreach(int i in intArray)
        Console.WriteLine(i);
}

unsafe static void UnsafeSquareArray(int[] pArr)
{
    int len = pArr.Length;

    //in C or C++, we could say
    // int* a = &(pArr[0])
    // however, C# requires you to "fix" the variable first 
    fixed(int* fixedPointer = &(pArr[0]))
    {
        //Declare a new int pointer because "fixedPointer" cannot be written to.
        // "p" points to the same address space, but we can modify it
        int* p = fixedPointer;

        for (int i = 0; i < len; i++)
        {
            *p *= *p; //square the value, just like we did in SquarePtrParam, above
            p++;      //move the pointer to the next memory space.
                      // NOTE that the pointer will move 4 bytes since "p" is an
                      // int pointer and an int takes 4 bytes

            //the above 2 lines could be written as one, like this:
            // "*p *= *p++;"
        }
    }
}

Выход:

1
4
9
16
25
36
49
64
81
100

unsafe также позволяет использовать stackalloc, который будет выделять память в стеке, например _alloca, в библиотеке времени выполнения C. Мы можем изменить приведенный выше пример, чтобы использовать stackalloc следующим образом:

unsafe void Main()
{
    const int len=10;
    int* seedArray = stackalloc int[len];
    
    //We can no longer use the initializer "{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}" as before.
    // We have at least 2 options to populate the array. The end result of either
    // option will be the same (doing both will also be the same here).

    //FIRST OPTION:
    int* p = seedArray; // we don't want to lose where the array starts, so we
                        // create a shadow copy of the pointer
    for(int i=1; i<=len; i++)
        *p++ = i;
    //end of first option

    //SECOND OPTION:
    for(int i=0; i<len; i++)
        seedArray[i] = i+1;
    //end of second option

    UnsafeSquareArray(seedArray, len);
    for(int i=0; i< len; i++)
        Console.WriteLine(seedArray[i]);
}

//Now that we are dealing directly in pointers, we don't need to mess around with
// "fixed", which dramatically simplifies the code
unsafe static void UnsafeSquareArray(int* p, int len)
{
    for (int i = 0; i < len; i++)
        *p *= *p++;
}

(Выход такой же, как и выше)

неявный

implicit ключевое слово используется для перегрузки оператора преобразования. Например, вы можете объявить класс Fraction который должен быть автоматически преобразован в double при необходимости и который может быть автоматически преобразован из int :

class Fraction(int numerator, int denominator)
{
    public int Numerator { get; } = numerator;
    public int Denominator { get; } = denominator;
    // ...
    public static implicit operator double(Fraction f)
    {
        return f.Numerator / (double) f.Denominator;
    }
    public static implicit operator Fraction(int i)
    {
        return new Fraction(i, 1);
    }
}

true, false

true и false ключевые слова имеют два применения:

  1. Как буквальные логические значения
var myTrueBool = true;
var myFalseBool = false;
  1. Как операторы, которые могут быть перегружены
public static bool operator true(MyClass x)
{
    return x.value >= 0;
}

public static bool operator false(MyClass x)
{
    return x.value < 0;
}

Перегрузка фальшивого оператора была полезной до C # 2.0 перед введением типов Nullable .
Тип, который перегружает true оператор, также должен перегружать false оператор.

строка

string - это псевдоним типа данных .NET System.String , который позволяет сохранять текст (последовательности символов).

Обозначения:

string a = "Hello";
var b = "world";
var f = new string(new []{ 'h', 'i', '!' }); // hi!

Каждый символ в строке кодируется в UTF-16, что означает, что для каждого символа требуется минимум 2 байта пространства для хранения.

USHORT

Числовой тип, используемый для хранения 16-битных положительных целых чисел. ushort - это псевдоним для System.UInt16 и занимает 2 байта памяти.

Допустимый диапазон от 0 до 65535 .

ushort a = 50; // 50
ushort b = 65536; // Error, cannot be converted
ushort c = unchecked((ushort)65536); // Overflows (wraps around to 0)

SByte

Числовой тип , используемый для хранения 8-битные целые числа. sbyte - это псевдоним для System.SByte и занимает 1 байт памяти. Для беззнакового эквивалента используйте byte .

Допустимый диапазон - от -127 до 127 (остаточный используется для хранения знака).

sbyte a = 127; // 127
sbyte b = -127; // -127
sbyte c = 200; // Error, cannot be converted
sbyte d = unchecked((sbyte)129); // -127 (overflows)

вар

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

var i = 10; // implicitly typed, the compiler must determine what type of variable this is
int i = 10; // explicitly typed, the type of variable is explicitly stated to the compiler

// Note that these both represent the same type of variable (int) with the same value (10).

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

var i;
i = 10;

// This code will not run as it is not initialized upon declaration.

Ключевое слово var также можно использовать для создания новых типов данных «на лету». Эти новые типы данных называются анонимными типами . Они весьма полезны, так как они позволяют пользователю определять набор свойств без необходимости явно объявлять тип какого-либо типа.

Обычный анонимный тип

var a = new { number = 1, text = "hi" };

Запрос LINQ, возвращающий анонимный тип

public class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public void GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
             join b in db.Breeds on d.BreedId equals b.BreedId
             select new 
                    {
                        DogName = d.Name,
                        BreedName = b.BreedName
                    };

    DoStuff(result);
}

Вы можете использовать ключевое слово var в инструкции foreach

public bool hasItemInList(List<String> list, string stringToSearch)
{
    foreach(var item in list)
    {
        if( ( (string)item ).equals(stringToSearch) )
            return true;
    }

    return false;
}

делегат

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

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

class DelegateExample
{
    public void Run()
    {
        //using class method
        InvokeDelegate( WriteToConsole ); 
        
        //using anonymous method
        DelegateInvoker di = delegate ( string input ) 
        { 
            Console.WriteLine( string.Format( "di: {0} ", input ) );
            return true; 
        };
        InvokeDelegate( di ); 
        
        //using lambda expression
        InvokeDelegate( input => false ); 
    }

    public delegate bool DelegateInvoker( string input );

    public void InvokeDelegate(DelegateInvoker func)
    {
        var ret = func( "hello world" );
        Console.WriteLine( string.Format( " > delegate returned {0}", ret ) );
    }

    public bool WriteToConsole( string input )
    {
        Console.WriteLine( string.Format( "WriteToConsole: '{0}'", input ) );
        return true;
    }
}

При назначении метода делегату важно отметить, что метод должен иметь одинаковый тип возвращаемого значения, а также параметры. Это отличается от «нормального» перегрузки метода, где только параметры определяют сигнатуру метода.

События создаются поверх делегатов.

событие

event позволяет разработчику внедрить шаблон уведомления.

Простой пример

public class Server
{
    // defines the event
    public event EventHandler DataChangeEvent;

    void RaiseEvent()
    {
        var ev = DataChangeEvent;
        if(ev != null)
        {
            ev(this, EventArgs.Empty);
        }
    }
}

public class Client
{
    public void Client(Server server)
    {
        // client subscribes to the server's DataChangeEvent
        server.DataChangeEvent += server_DataChanged;
    }

    private void server_DataChanged(object sender, EventArgs args)
    {
        // notified when the server raises the DataChangeEvent
    }
}

Ссылка на MSDN

частичный

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

File1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
    }
}

File2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
    }
}

Примечание. Класс можно разделить на любое количество файлов. Тем не менее, все объявления должны быть в том же пространстве имен и в той же сборке.

Методы также могут быть объявлены частично с использованием partial ключевого слова. В этом случае один файл будет содержать только определение метода, а другой файл будет содержать реализацию.

Частичный метод имеет свою подпись, определенную в одной части частичного типа, а ее реализация определена в другой части типа. Частичные методы позволяют разработчикам классов предлагать крючки методов, аналогичные обработчикам событий, которые разработчики могут решить реализовать или нет. Если разработчик не предоставляет реализацию, компилятор удаляет подпись во время компиляции. Для частичных методов применяются следующие условия:

  • Подписи в обеих частях частичного типа должны совпадать.
  • Метод должен возвращать void.
  • Модификаторы доступа не допускаются. Частичные методы неявно закрыты.

- MSDN

File1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
        public partial Method1(string str);
    }
}

File2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
        public partial Method1(string str)
        {
            Console.WriteLine(str);
        }
    }
}

Примечание . Тип, содержащий частичный метод, также должен быть объявлен частичным.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow