수색…


소개

C # 언어의 여섯 번째 반복은 Roslyn 컴파일러에서 제공합니다. 이 컴파일러는 .NET Framework 버전 4.6과 함께 제공되었지만 이전 프레임 워크 버전을 대상으로하도록 이전 버전과 호환되는 방식으로 코드를 생성 할 수 있습니다. C # 버전 6 코드는 완전히 이전 버전과 호환되는 방식으로 .NET 4.0으로 컴파일 될 수 있습니다. 이전 프레임 워크에도 사용할 수 있지만 추가 프레임 워크 지원이 필요한 일부 기능은 제대로 작동하지 않을 수 있습니다.

비고

C #의 여섯 번째 버전은 Visual Studio 2015 및 .NET 4.6과 함께 2015 년 7 월에 출시되었습니다.

새로운 언어 기능을 추가 할뿐만 아니라 컴파일러를 완전히 다시 작성합니다. 이전에 csc.exe 는 C ++로 작성된 기본 Win32 응용 프로그램 이었지만 C # 6에서는 C #으로 작성된 .NET 관리 응용 프로그램입니다. 이 재 작성은 "Roslyn"프로젝트로 알려져 있으며 코드는 현재 오픈 소스이며 GitHub에서 사용할 수 있습니다 .

연산자 이름

nameof 연산자는 코드 요소의 이름을 string 로 반환 string . 이것은 메소드 인자와 관련된 예외를 던지거나 INotifyPropertyChanged 구현할 때 유용합니다.

public string SayHello(string greeted)
{
    if (greeted == null)
        throw new ArgumentNullException(nameof(greeted));
    
    Console.WriteLine("Hello, " + greeted);
}

nameof 연산자는 컴파일 타임에 평가되고 표현식을 문자열 리터럴로 변경합니다. 이는 문자열을 노출하는 구성원 다음에 이름이 지정된 문자열에도 유용합니다. 다음을 고려하세요:

public static class Strings
{
    public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
    public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}

이후 nameof 표현이 컴파일시 상수, 그들은 속성에서 사용할 수있는 case , 라벨 switch 등등 문을합니다.


Enum 과 함께 nameof 를 사용하는 것이 편리합니다. 대신에:

Console.WriteLine(Enum.One.ToString());

다음을 사용할 수 있습니다.

Console.WriteLine(nameof(Enum.One))

두 경우 모두 출력이 One 됩니다.


nameof 연산자는 정적 인 구문을 사용하여 비 정적 멤버에 액세스 할 수 있습니다. 대신에 :

string foo = "Foo";
string lengthName = nameof(foo.Length);

대체 가능 :

string lengthName = nameof(string.Length);

두 출력 모두 Length 가 출력됩니다. 그러나 후자는 불필요한 인스턴스 생성을 방지합니다.


nameof 연산자는 대부분의 언어 구문에서 작동하지만 몇 가지 제한 사항이 있습니다. 예를 들어, 열려있는 제네릭 형식 또는 메서드 반환 값에 대해서는 nameof 연산자를 사용할 수 없습니다.

public static int Main()
{   
    Console.WriteLine(nameof(List<>)); // Compile-time error
    Console.WriteLine(nameof(Main())); // Compile-time error
}

또한이를 제네릭 형식에 적용하면 제네릭 형식 매개 변수가 무시됩니다.

Console.WriteLine(nameof(List<int>));  // "List"
Console.WriteLine(nameof(List<bool>)); // "List"

더 많은 예제는 nameof 전용 주제를 참조하십시오.


이전 버전의 해결 방법 ( 자세한 내용 )

nameof 연산자가 6.0 이전 버전의 C #에는 존재하지 않지만 다음과 같이 MemberExpression 을 사용하면 비슷한 기능을 사용할 수 있습니다.

6.0

표현:

public static string NameOf<T>(Expression<Func<T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

용법:

string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);

이 접근법은 모든 호출에서 표현식 트리를 생성하므로 컴파일 타임에 평가되고 런타임에 오버 헤드가없는 nameof 연산자와 비교할 때 성능이 훨씬 더 나쁘다는 점에 nameof .

표현식 본문 함수 멤버

표현식 본문 함수 멤버는 람다 식을 멤버 본문으로 사용할 수 있습니다. 간단한 멤버의 경우보다 명확하고 읽기 쉬운 코드가 될 수 있습니다.

표현식 본문 함수는 속성, 인덱서, 메서드 및 연산자에 사용할 수 있습니다.


속성

public decimal TotalPrice => BasePrice + Taxes;

다음과 같습니다.

public decimal TotalPrice
{
    get
    {
        return BasePrice + Taxes;
    }
}

표현식 본문 함수가 속성과 함께 사용되면 속성은 getter 전용 속성으로 구현됩니다.

데모보기


인덱서

public object this[string key] => dictionary[key];

다음과 같습니다.

public object this[string key]
{
    get
    {
        return dictionary[key];
    }
}

행동 양식

static int Multiply(int a, int b) => a * b;

다음과 같습니다.

static int Multiply(int a, int b)
{
    return a * b;
}

void 메소드와 함께 사용할 수도 있습니다.

public void Dispose() => resource?.Dispose();

ToString 의 재정의는 Pair<T> 클래스에 추가 될 수 있습니다.

public override string ToString() => $"{First}, {Second}";

또한이 단순한 접근 방식은 override 키워드를 사용하여 작동합니다.

public class Foo
{
    public int Bar { get; }

    public string override ToString() => $"Bar: {Bar}";
}

연산자

이것은 또한 운영자가 사용할 수 있습니다 :

public class Land
{
    public double Area { get; set; }

    public static Land operator +(Land first, Land second) =>
        new Land { Area = first.Area + second.Area };
}

제한 사항

표현식 본문 함수 멤버에는 몇 가지 제한 사항이 있습니다. 블록 문과 블록을 포함하는 다른 명령문을 포함 할 수 없습니다 : if , switch , for , foreach , while , do , try

일부 if 문은 삼항 연산자로 대체 될 수 있습니다. 일부 forforeach 문은 LINQ 쿼리로 변환 할 수 있습니다. 예를 들면 다음과 같습니다.

IEnumerable<string> Digits
{
    get
    {
        for (int i = 0; i < 10; i++)
            yield return i.ToString();
    }
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());

다른 모든 경우에는 함수 멤버에 대한 이전 구문을 사용할 수 있습니다.

표현식 본문 함수 멤버는 async / await 포함 할 수 있지만 종종 중복됩니다.

async Task<int> Foo() => await Bar();  

대체 가능 :

Task<int> Foo() => Bar();

예외 필터

예외 필터를 사용 하면 개발자는 조건을 boolean 식의 형태로 catch 블록에 추가하여 조건이 true 평가 될 때만 catch 를 실행할 수 true .

예외 필터를 사용하면 원래 예외에서 디버그 정보를 전파 할 수 있습니다. 여기서 catch 블록 내에서 if 문을 사용하고 예외를 다시 throw하면 원래 예외에서 디버그 정보 전파가 중단됩니다. 예외 필터를 사용하면 조건이 충족 되지 않으면 예외가 계속해서 호출 스택에 전파됩니다. 결과적으로 예외 필터는 디버깅 환경을 훨씬 쉽게 만듭니다. throw 문에서 멈추는 대신 디버거는 현재 상태와 모든 로컬 변수가 보존 된 상태에서 예외를 throw하는 명령문에서 멈 춥니 다. 크래시 덤프도 비슷한 방식으로 영향을받습니다.

예외 필터는 처음부터 CLR 에서 지원되었으며 CL.NET의 예외 처리 모델의 일부를 노출하여 10 년 이상 VB.NET 및 F #에서 액세스 할 수있었습니다. C # 6.0 릴리스 이후에만 기능이 C # 개발자에게 제공되었습니다.


예외 필터 사용

예외 필터는 catch 절에 when 절을 추가하여 사용됩니다. when 절에서 bool 을 반환하는 표현식을 사용할 수 있습니다 (단, await 제외). 선언 된 Exception 변수 exwhen 절에서 액세스 할 수 있습니다.

var SqlErrorToIgnore = 123;
try
{
    DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
    throw new Exception("An error occurred accessing the database", ex);
}

when 절이있는 여러 catch 블록이 결합 될 수 있습니다. true 를 반환하는 첫 번째 when 절은 예외가 포착되도록합니다. catch 블록이 입력되고 다른 catch 절은 무시됩니다 ( when 절은 평가되지 않음). 예 :

try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
                                          //the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
                                                       //someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }

위험한 when 절

주의

예외 필터를 사용하는 것이 위험 할 수 있습니다 때 Exception 내에서 발생되는 when , 절 Exception 으로부터 when 절이 무시되고로 처리됩니다 false . 이 접근법은 개발자가 잘못된 경우를 돌보지 않고 when 절을 작성할 수 있도록합니다.

다음 예제에서는 이러한 시나리오를 보여줍니다.

public static void Main()
{
    int a = 7;
    int b = 0;
    try
    {
        DoSomethingThatMightFail();
    }
    catch (Exception ex) when (a / b == 0)
    {
        // This block is never reached because a / b throws an ignored
        // DivideByZeroException which is treated as false.
    }
    catch (Exception ex)
    {
        // This block is reached since the DivideByZeroException in the 
        // previous when clause is ignored.
    }
}

public static void DoSomethingThatMightFail()
{
    // This will always throw an ArgumentNullException.
    Type.GetType(null);
}

데모보기

예외 필터를 사용하면 실패한 코드가 동일한 함수 내에있을 때 throw 를 사용하는 것과 관련된 혼동되는 행 번호 문제를 피할 수 있습니다. 예를 들어이 경우 행 번호는 3 대신 6으로보고됩니다.

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) {
6.     throw;
7. }

6 행의 throw 문으로 오류가 발견되어 다시 throw되기 때문에 예외 행 번호는 6으로보고됩니다.

예외 필터에서도 마찬가지입니다.

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6.     throw;
7. }

이 예에서 a 는 0이고 catch 절은 무시되지만 3은 줄 번호로보고됩니다. 이것은 스택을 풀지 않기 때문입니다. 구체적으로는, 예외 라인 (5)에 포착되지 않으므로 실제로 동일하지 a 0 따라서 라인 (6)가 실행되지 않기 때문에 라인 (6) 상에 다시 발생되는 예외를위한 기회가 없다.


부작용으로 로깅

조건의 메서드 호출은 부작용을 유발할 수 있으므로 예외 필터를 사용하여 예외를 catch하지 않고 예외에 대한 코드를 실행할 수 있습니다. 이 문제를 이용하는 일반적인 예로는 항상 false 반환하는 Log 메서드가 있습니다. 따라서 예외를 다시 throw 할 필요없이 디버깅 중에 로그 정보를 추적 할 수 있습니다.

이것이 로깅의 편안한 방법 인 것처럼 보이지만 특히 제 3 자 로깅 어셈블리가 사용되는 경우 위험 할 수 있음을 알아 두십시오 . 이것들은 쉽게 발견되지 않는 명백하지 않은 상황에서 로깅하는 동안 예외를 던질 수 있습니다 (위의 when(...) 절을 참조하십시오).

try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
    // This catch block will never be reached
}

// ...

static bool Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
    return false;
}

데모보기

이전 버전의 C #에서 일반적인 접근 방식은 예외를 기록하고 다시 throw하는 것이 었습니다.

6.0
try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
     Log(ex, "An error occurred");
     throw;
}

// ...

static void Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
}

데모보기


finally 블록

finally 블록은 예외가 throw되는지 여부와 관계없이 항상 실행됩니다. 예외 필터가있는 when 표현식이있는 하나의 미묘함은 내부 finally 블록을 입력 하기 전에 스택에서 더 위로 실행됩니다. 코드가 전역 상태 (예 : 현재 스레드의 사용자 또는 culture)를 수정하고 finally 블록에 다시 설정하려고하면 예기치 않은 결과와 동작이 발생할 수 있습니다.

예 : finally 블록

private static bool Flag = false;

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

생산량 :

스타트
EvaluatesTo : True
마지막으로 내부
잡기
마지막으로 바깥 쪽

데모보기

위의 예제에서 SomeOperation 메서드가 전역 상태 변경을 호출자의 when 절로 "누설"하지 않으려면 상태를 수정하는 catch 블록도 포함해야합니다. 예 :

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    catch
    {
       Flag = false;
       throw;
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

IDisposable.Dispose 는 항상 using 블록 내에서 호출 된 예외가 스택을 버블 링하기 전에 호출되므로 동일한 목표를 달성하기 위해 블록 사용 의 의미를 활용하는 IDisposable 도우미 클래스를 보는 것이 일반적입니다.

자동 속성 초기화 도구

소개

속성은 닫힌 뒤에 } = 연산자로 초기화 할 수 있습니다. 아래 Coordinate 클래스는 속성을 초기화하는 데 사용할 수있는 옵션을 보여줍니다.

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

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

가시성이 다른 접근 자

접근 자에 대한 가시성이 다른 자동 속성을 초기화 할 수 있습니다. 다음은 보호 된 설정자를 사용한 예입니다.

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

접근 internal , internal protected 또는 private 일 수도 있습니다.


읽기 전용 속성

가시성이있는 유연성 외에도 읽기 전용 자동 속성을 초기화 할 수도 있습니다. 다음은 그 예입니다.

    public List<string> Ingredients { get; } = 
        new List<string> { "dough", "sauce", "cheese" };

이 예제는 복잡한 유형의 속성을 초기화하는 방법도 보여줍니다. 또한 자동 속성은 쓰기 전용 일 수 없으므로 쓰기 전용 초기화도 배제됩니다.


이전 스타일 (C # 6.0 이전)

C # 6 이전에는 훨씬 더 자세한 코드가 필요했습니다. 우리는 속성에 대해 하나의 추가 변수를 사용하여 기본값을 부여하거나 아래의 public 속성을 초기화했습니다.

6.0
public class Coordinate
{
    private int _x = 34;
    public int X { get { return _x; } set { _x = value; } }

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

    public Coordinate()
    {
        _z = 42;
    }
}

참고 : C # 6.0 이전에는 생성자 내에서 자동 구현 된 속성 (getter 및 setter를 사용하여 속성)을 읽고 초기화 할 수 있었지만 속성을 선언과 함께 초기화 할 수는 없었습니다.

데모보기


용법

초기화 프로그램은 필드 초기화 프로그램과 마찬가지로 정적 식으로 평가되어야합니다. 비 정적 멤버를 참조해야하는 경우 이전처럼 생성자에서 속성을 초기화하거나 표현 본문 속성을 사용할 수 있습니다. 아래 (주석 처리 된)와 같은 비 정적 표현식은 컴파일러 오류를 생성합니다.

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

decimal InitMe() { return 4m; }

그러나 정적 메서드 사용하여 자동 속성을 초기화 할 수 있습니다.

public class Rectangle
{
    public double Length { get; set; } = 1;
    public double Width { get; set; } = 1;
    public double Area { get; set; } = CalculateArea(1, 1);

    public static double CalculateArea(double length, double width)
    {
        return length * width;
    }
}

이 메서드는 접근 자의 수준이 다른 속성에도 적용 할 수 있습니다.

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

자동 속성 초기화 프로그램을 사용하면 선언 내에 속성을 직접 할당 할 수 있습니다. 읽기 전용 속성의 경우 속성을 변경할 수 없도록하는 데 필요한 모든 요구 사항을 처리합니다. 다음 예제에서 FingerPrint 클래스를 예로 들어 보겠습니다.

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

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

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

데모보기


주의 사항

비슷한 찾고 자동 속성 또는 필드 초기화 혼동하지 않도록주의 표현 바디 방법 을 사용하기 => 반대 = 포함되지 않으며, 필드 { get; } .

예를 들어, 다음 선언은 각각 다릅니다.

public class UserGroupDto
{
    // Read-only auto-property with initializer:       
    public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
    
    // Read-write field with initializer:
    public ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // Read-only auto-property with expression body:
    public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}

누락 { get; } 속성 선언에서 공용 필드가 발생합니다. 읽기 전용 자동 속성 Users1 과 읽기 / 쓰기 필드 Users2 는 모두 한 번만 초기화되지만 공용 필드는 클래스 외부에서 컬렉션 인스턴스를 변경할 수 있으므로 일반적으로 바람직하지 않습니다. 표현식 본문이있는 읽기 전용 자동 속성을 이니셜 라이저가있는 읽기 전용 속성으로 변경하려면 > from => 제거 할 필요가 있지만 { get; } .

HashSet<UserDto> 의 새 인스턴스를 반환하는 속성에 대한 각 액세스는 Users3 의 다른 기호 ( => 대신 = )를 사용 Users3 컴파일러의 관점에서 볼 때 올바른 C #은 원하는 동작이 아닐 것입니다. 컬렉션 멤버로 사용됩니다.

위의 코드는 다음과 같습니다.

public class UserGroupDto
{
    // This is a property returning the same instance
    // which was created when the UserGroupDto was instantiated.
    private ICollection<UserDto> _users1 = new HashSet<UserDto>();
    public ICollection<UserDto> Users1 { get { return _users1; } }

    // This is a field returning the same instance
    // which was created when the UserGroupDto was instantiated.
    public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // This is a property which returns a new HashSet<UserDto> as
    // an ICollection<UserDto> on each call to it.
    public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}

인덱스 이니셜 라이저

인덱스 이니셜 라이저를 사용하면 인덱스를 사용하여 동시에 객체를 만들고 초기화 할 수 있습니다.

이렇게하면 사전을 매우 쉽게 초기화 할 수 있습니다.

var dict = new Dictionary<string, int>()
{
    ["foo"] = 34,
    ["bar"] = 42
};

인덱싱 된 getter 또는 setter가있는 객체는 다음 구문과 함께 사용할 수 있습니다.

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42
        };

        Console.ReadKey();
    }
}

산출:

색인 : foo, 값 : 34
색인 : 막대, 값 : 42

데모보기

클래스에 여러 인덱서가 있으면 단일 명령문 그룹에 모두 인덱서를 지정할 수 있습니다.

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
        public string this[int index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42,
            [10] = "Ten",
            [42] = "Meaning of life"
        };
    }
}

산출:

색인 : foo, 값 : 34
색인 : 막대, 값 : 42
색인 : 10, 값 : 10
색인 : 42, 가치 : 삶의 의미

인덱서 set 접근자는 (컬렉션 초기화 프로그램에서 사용되는) Add 메서드와 다르게 동작 할 수 있습니다.

예 :

var d = new Dictionary<string, int>
{
    ["foo"] = 34,
    ["foo"] = 42,
}; // does not throw, second value overwrites the first one

대:

var d = new Dictionary<string, int>
{
    { "foo", 34 },
    { "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.

문자열 보간법

문자열 보간은 variables 와 텍스트를 결합하여 문자열을 형성 할 수있게합니다.


기본 예제

두 개의 int 변수 foobar 가 생성 bar .

int foo = 34;
int bar = 42;

string resultString = $"The foo is {foo}, and the bar is {bar}.";

Console.WriteLine(resultString);

출력 :

foo는 34이고 bar는 42입니다.

데모보기

문자열 내에서 다음과 같이 중괄호를 사용할 수 있습니다.

var foo = 34;
var bar = 42;

// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");

그러면 다음과 같은 결과가 출력됩니다.

foo는 {foo}이고 bar는 {bar}입니다.


축 어적 문자열 리터럴을 사용한 보간 사용

문자열 앞에 @ 를 사용하면 문자열이 그대로 해석됩니다. 따라서 유니 코드 문자 나 줄 바꿈은 입력 한 그대로 유지됩니다. 그러나 다음 예제와 같이 보간 된 문자열의 식에는 영향을주지 않습니다.

Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
산출:

분명하지 않은 경우 :
\ u00B9
푸우
34,

42입니다.

데모보기


표현식

문자열 보간을 사용하면 중괄호 {} 내의 표현식 도 평가할 수 있습니다. 결과는 문자열 내의 해당 위치에 삽입됩니다. 예를 들어 foobar 의 최대 값을 계산하여 삽입하려면 중괄호 안에 Math.Max 사용합니다.

Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");

산출:

그리고 더 큰 것은 : 42

주 : 중괄호와 표현식 사이의 앞뒤 공백 (공백, 탭 및 CRLF / 개행 문자 포함)은 완전히 무시되며 출력에는 포함되지 않습니다.

데모보기

또 다른 예로, 변수는 통화 형식으로 지정할 수 있습니다.

Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");

산출:

Foo는 소수 자릿수 4 자리로 통화 형식을 지정했습니다 : $ 34.0000

데모보기

또는 날짜 형식으로 지정할 수 있습니다.

Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");

산출:

오늘은 : 7 월 20 일 월요일, 2015 년

데모보기

조건부 (3 진) 연산자 가있는 명령문도 보간법 내에서 평가할 수 있습니다. 그러나 콜론은 위와 같이 서식을 지정하는 데 사용되므로 다음은 괄호로 묶어야합니다.

Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");

산출:

바는 foo보다 큽니다!

데모보기

조건식과 형식 지정자를 혼합 할 수 있습니다.

Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");

산출:

환경 : 32 비트 프로세스


이스케이프 시퀀스

백 슬래시 ( \ )와 인용 부호 ( " )를 이스케이프 처리하는 것은 비 보간 문자열에서와 같이 보간 문자열에서 동일하게 작동합니다. 즉, 그대로 및 비 동사 문자열 리터럴 모두에 대해 작동합니다.

Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");

산출:

Foo는 34입니다. 비 축 어적 문자열에서 "와 \를 백 슬래시로 이스케이프 처리해야합니다.
Foo는 34입니다. 축 어적 문자열에서 "따옴표를 사용하여 이스케이프해야하지만 우리는 이스케이프 할 필요가 없습니다 \

보간 문자열에 중괄호 { 또는 } 를 포함 시키려면 두 개의 중괄호 {{ 또는 }} .

$"{{foo}} is: {foo}"

산출:

{foo} : 34

데모보기


FormattableString 유형

$"..." 문자열 보간 표현식의 유형은 항상 단순한 문자열 이 아닙니다 . 컴파일러는 컨텍스트에 따라 할당 할 유형을 결정합니다.

string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";

이것은 또한 컴파일러가 어떤 오버로드 된 메소드가 호출 될지 선택해야 할 때 타입 선호도의 순서입니다.

새로운 형식System.FormattableString 은 형식화 할 인수와 함께 복합 형식 문자열을 나타냅니다. 보간 인수를 특별히 처리하는 응용 프로그램을 작성하려면 다음을 사용하십시오.

public void AddLogItem(FormattableString formattableString)
{
    foreach (var arg in formattableString.GetArguments())
    {
        // do something to interpolation argument 'arg'
    }

    // use the standard interpolation and the current culture info
    // to get an ordinary String:
    var formatted = formattableString.ToString();

    // ...
}

위 메소드를 다음과 같이 호출하십시오.

AddLogItem($"The foo is {foo}, and the bar is {bar}.");
예를 들어, 로깅 수준에서 이미 로그 항목을 필터링하려고하는 경우 문자열 서식 지정의 성능 비용이 발생하지 않도록 선택할 수 있습니다.

암시 적 전환

보간 된 문자열의 암시 적 유형 변환은 다음과 같습니다.

var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
불변 컨텍스트로 문자열을 변환 할 수있는 IFormattable 변수를 생성 할 수도 있습니다.
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";

현재 및 불변 문화 방법

코드 분석이 켜지면 보간 된 문자열은 모두 경고 CA1305 (Specify IFormatProvider )를 생성합니다. 정적 메서드는 현재 문화권을 적용하는 데 사용할 수 있습니다.

public static class Culture
{
    public static string Current(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.CurrentCulture);
    }
    public static string Invariant(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.InvariantCulture);
    }
}

그런 다음 현재 문화권에 올바른 문자열을 생성하려면 다음 표현식을 사용하십시오.

Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
참고 : Current 하고 Invariant 기본적으로 컴파일러가 타입에 할당하기 때문에 확장 방법으로 생성 할 수없는 String 컴파일하는 데 실패 다음 코드를 발생 보간 문자열 표현 :

$"interpolated {typeof(string).Name} string.".Current();

FormattableString 클래스에는 이미 Invariant() 메서드가 포함되어 있으므로 고정 문화권으로 전환하는 가장 간단한 방법은 using staticusing static 하는 것입니다.

using static System.FormattableString;

string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";


무대 뒤에서

보간 된 문자열은 String.Format() 의 문법적 설탕 일뿐입니다. 컴파일러 ( Roslyn )는 String.Format 으로 변환합니다.

var text = $"Hello {name + lastName}";

위의 내용은 다음과 같이 변환 될 것입니다 :

string text = string.Format("Hello {0}", new object[] {
    name + lastName
});

문자열 보간 및 Linq

Linq 문에 보간 된 문자열을 사용하여 가독성을 더욱 높일 수 있습니다.

var fooBar = (from DataRow x in fooBarTable.Rows
          select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();

다음과 같이 다시 쓸 수 있습니다 :

var fooBar = (from DataRow x in fooBarTable.Rows
          select $"{x["foo"]}{x["bar"]}").ToList();

재사용 가능한 보간 된 문자열

string.Format 사용하면 재사용 가능한 형식 문자열을 만들 수 있습니다.

public const string ErrorFormat = "Exception caught:\r\n{0}";

// ...

Logger.Log(string.Format(ErrorFormat, ex));

그러나 보간 된 문자열은 존재하지 않는 변수를 나타내는 자리 표시 자로 컴파일되지 않습니다. 다음은 컴파일되지 않습니다.

public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context

대신 변수를 사용하고 String 반환하는 Func<> 을 만듭니다.

public static Func<Exception, string> FormatError =
    error => $"Exception caught:\r\n{error}";

// ...

Logger.Log(FormatError(ex));

문자열 보간 및 지역화

애플리케이션을 현지화하는 경우 현지화와 함께 문자열 보간을 사용할 수 있는지 궁금 할 수 있습니다. 실제로 리소스 파일에 String 을 저장하는 것이 좋습니다.

"My name is {name} {middlename} {surname}"
훨씬 덜 읽을 수있는 대신 :

"My name is {0} {1} {2}"

String 보간 처리 는 런타임에 발생하는 string.Format 을 사용하여 문자열을 서식 지정하는 것과 달리 컴파일 할 때 발생 합니다 . 삽입 된 문자열의 표현식은 현재 컨텍스트의 이름을 참조해야하며 리소스 파일에 저장해야합니다. 즉, 현지화를 사용하려면 다음과 같이해야합니다.

var FirstName = "John";

// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"), 
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
    // get localized string
    var localizedMyNameIs = Properties.strings.Hello;
    // insert spaces where necessary
    name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
    middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
    surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
    // display it
    Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}

// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);

// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);

// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);

위에 사용 된 언어의 리소스 문자열이 개별 리소스 파일에 올바르게 저장되어 있으면 다음과 같은 결과가 나타납니다.

Bonjour, mon nom est John
안녕하세요. 이름이 존 이예요.
안녕, 내 이름은 존이야

이 이름은 모든 언어로 지역화 된 문자열을 다음 있음을 의미합니다. 그렇지 않은 경우 리소스 문자열에 자리 표시자를 추가하고 위의 함수를 수정하거나 함수의 culture 정보를 쿼리하고 다른 경우를 포함하는 switch case 문을 제공해야합니다. 리소스 파일에 대한 자세한 내용은 C #에서 지역화 사용 방법을 참조하십시오.

번역을 사용할 수없는 경우 대부분의 사람들이 이해할 수있는 기본 대체 언어를 사용하는 것이 좋습니다. 기본 대체 언어로 영어를 사용하는 것이 좋습니다.

재귀 보간법

매우 유용하지는 않지만 다른 중괄호 안에 반복적으로 보간 된 string 을 사용할 수 있습니다.

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

산출:

문자열에는 27 개의 문자가 있습니다.

내 수업은 MyClass입니다.

마침내 잡으려고 기다린다.

사용할 수 있습니다 await 적용 할 식을 연산자를 기다리고작업 또는 (TResult의) 작업catchfinally C # 6에서 블록.

컴파일러 제한으로 인해 이전 버전의 catchfinally 블록에서 await 표현식을 사용할 수 없었습니다. C # 6은 수 있도록함으로써 훨씬 쉽게 비동기 작업을 기다리고 있습니다 await 식을.

try
{
    //since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    //since C#6
    await logger.LogAsync(e);
}
finally
{
    //since C#6
    await service.CloseAsync();
}

C # 5에서는 bool 을 사용하거나 try catch 외부의 Exception 선언하여 비동기 작업을 수행해야했습니다. 이 메서드는 다음 예제와 같습니다.

bool error = false;
Exception ex = null;

try
{
    // Since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    // Declare bool or place exception inside variable
    error = true;
    ex = e;
}

// If you don't use the exception
if (error)
{
    // Handle async task
}

// If want to use information from the exception
if (ex != null)
{
    await logger.LogAsync(e);
}    

// Close the service, since this isn't possible in the finally
await service.CloseAsync();

널 전파

?. 연산자 및 ?[...] 연산자를 null 조건부 연산자 라고합니다. 또한 안전 탐색 연산자 와 같은 다른 이름으로 참조되기도합니다.

이것은 유용합니다 . (구성원 접근 자) 연산자가 null 평가되는 식에 적용되면 프로그램은 NullReferenceException 을 throw합니다. 개발자가 대신 ?. (null-conditional) 연산자를 사용하면 예외를 throw하는 대신 표현식이 null로 평가됩니다.

만약 ?. 연산자가 사용되고 표현식이 널이 아닌 경우 ?. 그리고 . 동등하다.


기초

var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null

데모보기

classroom 에 교사가 없으면 GetTeacher()null 반환 할 수 있습니다. Namenull 이고 Name 속성에 액세스하면 NullReferenceException 이 throw됩니다.

이 문을 수정하여 ?. 구문을 사용하면 전체 표현식의 결과는 null .

var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null

데모보기

이후 classroomnull 일 수있는 경우이 문을 다음과 같이 작성할 수 있습니다.

var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null

데모보기

다음은 단락의 예입니다. 널 조건부 연산자를 사용하는 조건부 액세스 연산이 널 (NULL)로 평가되면 체인의 나머지를 처리하지 않고 전체 표현식이 즉시 null로 평가됩니다.

null 조건부 연산자를 포함하는 식의 터미널 멤버가 값 형식 인 경우 식은 해당 형식의 Nullable<T> 를 계산하므로 ?. 없이 식 대신 직접 사용할 수는 없습니다 ?. .

bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime

bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed

bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null

bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable

Null 통합 운영자와 함께 사용 (??)

null 조건부 연산자를 Null 통합 연산자 ( ?? ) 와 결합 하여 표현식이 null 해석되면 기본값을 반환 할 수 있습니다. 위의 예를 사용하면 다음과 같습니다.

var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher() 
// returns null OR classroom is null OR Name is null

인덱서와 함께 사용

null 조건부 연산자는 인덱서 와 함께 사용할 수 있습니다.

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

위의 예에서 :

  • 첫 번째 ?. classroomnull 이 아니라는 것을 보증합니다.
  • 두 번째 ? Students 컬렉션 전체가 null 이 아니도록 보장합니다.
  • 세 번째 ?. 인덱서가 [0] 인덱서가 null 객체를 반환하지 않도록합니다. 이 조작은 여전히 IndexOutOfRangeException 던질 수 있습니다 .

void 함수와 함께 사용

null 조건부 연산자는 void 함수와 함께 사용할 수도 있습니다. 그러나이 경우 명령문은 null 평가되지 않습니다. 그냥 NullReferenceException 방지합니다.

List<string> list = null;
list?.Add("hi");          // Does not evaluate to null

이벤트 호출과 함께 사용

다음 이벤트 정의를 가정합니다.

private event EventArgs OnCompleted;

이벤트를 호출 할 때 전통적으로 구독자가없는 경우 이벤트가 null 인지 확인하는 것이 좋습니다.

var handler = OnCompleted;
if (handler != null)
{
    handler(EventArgs.Empty);
}

null 조건부 연산자가 도입 되었기 때문에 호출을 한 줄로 줄일 수 있습니다.

OnCompleted?.Invoke(EventArgs.Empty);

제한 사항

Null 조건부 연산자는 lvalue가 아닌 rvalue를 생성합니다. 즉, 속성 할당, 이벤트 구독 등에 사용할 수 없습니다. 예를 들어 다음 코드는 작동하지 않습니다.

// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;

잡았다

참고 사항 :

int? nameLength = person?.Name.Length;    // safe if 'person' is null

다음과 같은 것은 아닙니다 :

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

전자가 다음에 해당하기 때문입니다.

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

후자는 다음에 해당합니다.

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

삼항 연산자에도 불구하고 ?: 이 두 가지 경우의 차이점을 설명하기 위해 여기에 사용됩니다.이 연산자는 동일하지 않습니다. 다음 예제를 통해 쉽게 설명 할 수 있습니다.

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

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

class Foo
{
    public string Bar
    {
        get
        {
            Console.WriteLine("I was read");
            return string.Empty;
        }
    }
}

어느 출력 :

널 전파
나는 읽 혔다.
0
세 개 한 벌
나는 읽 혔다.
나는 읽 혔다.
0

데모보기

여러 호출을 피하려면 다음과 같이하십시오.

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

그리고이 차이는 널 전파 연산자가 아직 표현식 트리에서 지원되지 않는 이유를 다소 설명합니다.

정적 유형 사용

using static [Namespace.Type] 지시문을 사용하면 형식 및 열거 형 값의 정적 멤버를 가져올 수 있습니다. 확장 메서드는 최상위 범위가 아닌 확장 메서드로 가져옵니다.

6.0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

라이브 데모 바이올린

6.0
using System;

class Program
{
    static void Main()
    {
        Console.BackgroundColor = ConsoleColor.DarkBlue;
        Console.WriteLine(Math.Sqrt(2));
    }
}

향상된 과부하 해결

다음 스 니펫은 델리게이트가 예상 될 때 메서드 그룹 (람다와 반대)을 전달하는 예를 보여줍니다. C # 6 에서 전달 된 메서드의 반환 형식을 확인하는 기능 때문에 모호한 오버로드 오류가 발생하는 대신 오버로드 해결로 해결됩니다.

using System;
public class Program
{
    public static void Main()
    {
        Overloaded(DoSomething);
    }

    static void Overloaded(Action action)
    {
       Console.WriteLine("overload with action called");
    }

    static void Overloaded(Func<int> function)
    {
       Console.WriteLine("overload with Func<int> called");
    }

    static int DoSomething()
    {
        Console.WriteLine(0);
        return 0;
    }
}

결과 :

6.0

산출

Func <int>라는 overload가 호출되었습니다.

데모보기

5.0

오류

오류 CS0121 : 'Program.Overloaded (System.Action)'및 'Program.Overloaded (System.Func)'메서드 또는 속성 사이의 호출이 모호합니다.

C # 6C # 5 에서 오류가 발생한 람다 식에 대한 정확한 일치의 다음 사례를 잘 처리 할 수도 있습니다.

using System;

class Program
{
    static void Foo(Func<Func<long>> func) {}
    static void Foo(Func<Func<int>> func) {}

    static void Main()
    {
        Foo(() => () => 7);
    }
}

사소한 변경 사항 및 버그 수정

명명 된 매개 변수 주위에 괄호를 사용할 수 없습니다. 다음은 C # 5에서는 컴파일되지만 C # 6에서는 컴파일되지 않습니다.

5.0
Console.WriteLine((value: 23));

의 피연산자 isas 더 이상 방법 그룹이 될 수 없습니다. 다음은 C # 5에서는 컴파일되지만 C # 6에서는 컴파일되지 않습니다.

5.0
var result = "".Any is byte;

네이티브 컴파일러는 (경고를 표시 했음에도 불구하고) 이것을 허용했지만 실제로 확장 메서드 호환성을 검사하지 않았기 때문에 1.Any is string 또는 IDisposable.Dispose is object 와 같은 미친 것들을 허용 IDisposable.Dispose is object .

변경 사항에 대한 업데이트는 이 참조 서를 참조 하십시오.

컬렉션 초기화에 확장 메서드 사용

컬렉션 초기화 구문은 IEnumerable 을 구현하는 클래스를 인스턴스화 할 때 사용할 수 있으며 단일 매개 변수를 사용하는 Add 라는 메서드가 있습니다.

이전 버전에서는이 Add 메서드가 초기화 될 클래스의 인스턴스 메서드 여야했습니다. C # 6에서는 확장 메서드가 될 수도 있습니다.

public class CollectionWithAdd : IEnumerable
{
    public void Add<T>(T item)
    {
        Console.WriteLine("Item added with instance add method: " + item);
    }

    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public class CollectionWithoutAdd : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public static class Extensions
{
    public static void Add<T>(this CollectionWithoutAdd collection, T item)
    {
        Console.WriteLine("Item added with extension add method: " + item);
    }
}

public class Program
{
    public static void Main()
    {
        var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
        var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
    }
}

그러면 다음과 같이 출력됩니다.

인스턴스 추가 메소드로 추가 된 항목 : 1
인스턴스 추가 메소드로 추가 된 항목 : 2
인스턴스 추가 메소드로 추가 된 항목 : 3
확장 기능 추가 메소드 : 4
확장 기능 추가 메소드 : 5
확장 기능 추가 메소드 : 6

경고 기능 사용 안 함

C # 5.0 및 이전 버전에서는 개발자가 숫자로만 경고를 표시 할 수있었습니다. Roslyn Analyzers가 도입됨에 따라 C #은 특정 라이브러리에서 발행 된 경고를 비활성화하는 방법이 필요합니다. C # 6.0에서는 pragma 지시어가 경고를 표시하지 않을 수 있습니다.

전에:

#pragma warning disable 0501

C # 6.0 :

#pragma warning disable CS0501


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow