수색…


소개

C # 7.0은 C #의 7 번째 버전입니다. 이 버전에는 튜플에 대한 언어 지원, 로컬 함수, out var 선언, 숫자 구분 기호, 바이너리 리터럴, 패턴 일치, 표현식 ref return , ref returnref local 및 확장 표현 본문 멤버 목록 등의 새로운 기능이 포함되어 있습니다.

공식 참조 : C # 7의 새로운 기능

out var 선언

C #의 일반적인 패턴은 bool TryParse(object input, out object value) 를 안전하게 구문 분석하기 위해 bool TryParse(object input, out object value) 를 사용합니다.

out var 선언은 가독성을 높이는 간단한 기능입니다. 변수를 out 매개 변수로 전달하는 것과 동시에 변수를 선언 할 수 있습니다.

이 방법으로 선언 된 변수는 선언 된 지점에서 나머지 본문으로 범위가 지정됩니다.

C # 7.0 이전의 TryParse 사용하면 함수를 호출하기 전에 값을받을 변수를 선언해야합니다.

7.0
int value;
if (int.TryParse(input, out value)) 
{
    Foo(value); // ok
}
else
{
    Foo(value); // value is zero
}

Foo(value); // ok

C # 7.0에서는 out 매개 변수에 전달 된 변수의 선언을 인라인 할 수 있으므로 별도의 변수 선언이 필요 없습니다.

7.0
if (int.TryParse(input, out var value)) 
{
    Foo(value); // ok
}
else
{
    Foo(value); // value is zero
}

Foo(value); // still ok, the value in scope within the remainder of the body

함수에서 반환하는 매개 변수의 일부 경우 out 필요하지 않습니다 당신은 폐기 연산자를 사용할 수 있습니다 _ .

p.GetCoordinates(out var x, out _); // I only care about x

out var 선언은 이미이 기존의 기능을 사용할 수 있습니다 out 매개 변수를. 함수 선언 구문은 동일하게 유지되며 함수가 out var 선언과 호환되도록하기 위해 추가 요구 사항이 필요하지 않습니다. 이 기능은 단순히 구문 설탕입니다.

out var 선언의 또 다른 특징은 익명 타입과 함께 사용할 수 있다는 것입니다.

7.0
var a = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var groupedByMod2 = a.Select(x => new
                                  {
                                      Source = x,
                                      Mod2 = x % 2
                                  })
                     .GroupBy(x => x.Mod2)
                     .ToDictionary(g => g.Key, g => g.ToArray());
if (groupedByMod2.TryGetValue(1, out var oddElements))
{
    Console.WriteLine(oddElements.Length);
}

이 코드에서 우리는 int key와 익명 타입 값의 배열로 Dictionary 를 생성합니다. 이전 버전의 C #에서는 TryGetValue 메서드를 사용할 수 TryGetValue 습니다 (익명 형식 인 out 변수를 선언해야했기 때문입니다). 그러나 out var 을 사용하면 out 변수의 유형을 명시 적으로 지정할 필요가 없습니다.

제한 사항

표현식이 표현식 람다 본문으로 해석되므로 var 선언은 LINQ 쿼리에서 제한적으로 사용되므로 도입 된 변수의 범위는 이러한 lambdas로 제한됩니다. 예를 들어 다음 코드는 작동하지 않습니다.

var nums = 
    from item in seq
    let success = int.TryParse(item, out var tmp)
    select success ? tmp : 0; // Error: The name 'tmp' does not exist in the current context

참고 문헌

이진 리터럴

0b 접두어는 이진 리터럴을 나타내는 데 사용할 수 있습니다.

바이너리 리터럴을 사용하면 0과 1에서 숫자를 구성 할 수 있으므로 숫자의 2 진 표현에 어떤 비트가 설정되어 있는지 쉽게 알 수 있습니다. 바이너리 플래그로 작업 할 때 유용 할 수 있습니다.

다음은 값 34 (= 2 5 + 2 1 )로 int 를 지정하는 것과 동일한 방법입니다.

// Using a binary literal:
//   bits: 76543210
int a1 = 0b00100010;          // binary: explicitly specify bits

// Existing methods:
int a2 = 0x22;                // hexadecimal: every digit corresponds to 4 bits
int a3 = 34;                  // decimal: hard to visualise which bits are set
int a4 = (1 << 5) | (1 << 1); // bitwise arithmetic: combining non-zero bits

플래그 열거 형

이전에이 예제에서 세 가지 방법 중 하나를 사용하여 enum 대한 플래그 값을 지정할 수있었습니다.

[Flags]
public enum DaysOfWeek
{
    // Previously available methods:
    //          decimal        hex       bit shifting
    Monday    =  1,    //    = 0x01    = 1 << 0
    Tuesday   =  2,    //    = 0x02    = 1 << 1
    Wednesday =  4,    //    = 0x04    = 1 << 2
    Thursday  =  8,    //    = 0x08    = 1 << 3
    Friday    = 16,    //    = 0x10    = 1 << 4
    Saturday  = 32,    //    = 0x20    = 1 << 5
    Sunday    = 64,    //    = 0x40    = 1 << 6

    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekends = Saturday | Sunday
}

바이너리 리터럴을 사용하면 어떤 비트가 설정되어 있는지 더 분명하게 알 수 있으며 16 진수 및 비트 연산을 이해할 필요가 없습니다.

[Flags]
public enum DaysOfWeek
{
    Monday    = 0b00000001,
    Tuesday   = 0b00000010,
    Wednesday = 0b00000100,
    Thursday  = 0b00001000,
    Friday    = 0b00010000,
    Saturday  = 0b00100000,
    Sunday    = 0b01000000,

    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekends = Saturday | Sunday
}

자릿수 구분 기호

밑줄 _ 은 숫자 분리 기호로 사용할 수 있습니다. 큰 숫자 리터럴에서 숫자를 그룹화 할 수 있다는 것은 가독성에 중요한 영향을 미칩니다.

밑줄은 아래에 명시된 경우를 제외하고 숫자 리터럴의 아무 곳에 나 나타날 수 있습니다. 서로 다른 그룹은 다른 시나리오 나 다른 숫자 기반으로 이해할 수 있습니다.

모든 자릿수는 하나 이상의 밑줄로 구분할 수 있습니다. _ 는 소수뿐만 아니라 지수로도 허용됩니다. 구분 기호는 의미 론적 영향을 미치지 않으며 단순히 무시됩니다.

int bin = 0b1001_1010_0001_0100;
int hex = 0x1b_a0_44_fe;
int dec = 33_554_432;
int weird = 1_2__3___4____5_____6______7_______8________9;
double real = 1_000.111_1e-1_000;

_ 숫자 분리 기호를 사용할 수없는 경우 :

  • 가치의 시작 부분 ( _121 )
  • 값의 마지막에 ( 121_ 또는 121.05_ )
  • 10 진수 옆에 ( 10_.0 )
  • 지수 문자 ( 1.1e_1 ) 옆에
  • 형식 지정자 ( 10_f ) 옆에
  • 0x 또는 0b 의 2 진수 및 16 진수 리터럴 바로 다음 ( 예 : 0b_1001_1000을 허용하도록 변경 될 수 있음 )

튜플 언어 지원

기초

튜플 은 요소의 정렬 된 유한 목록입니다. 튜플은 튜플의 각 요소를 개별적으로 작업하는 대신 하나의 엔티티로 집합 적으로 작업하고 관계형 데이터베이스에서 개별 레코드 (즉, 레코드)를 표현하는 수단으로 프로그래밍에서 일반적으로 사용됩니다.

C # 7.0에서는 메서드가 여러 반환 값을 가질 수 있습니다. 뒤에서 컴파일러는 새로운 ValueTuple 구조체를 사용합니다.

public (int sum, int count) GetTallies() 
{
    return (1, 2);
}

참고 : Visual Studio 2017에서이 작업을 수행하려면 System.ValueTuple 패키지를 System.ValueTuple 합니다.

튜플을 반환하는 메서드 결과가 단일 변수에 할당되면 메서드 시그니처에 정의 된 이름을 사용하여 멤버에 액세스 할 수 있습니다.

var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2

튜플 해체

튜플 해체 (tuple deconstruction)는 튜플을 해당 부분으로 분리합니다.

예를 들어, GetTallies 를 호출하고 두 개의 개별 변수에 반환 값을 할당하면 튜플을 두 변수로 분해합니다.

(int tallyOne, int tallyTwo) = GetTallies();

var 도 작동합니다.

(var s, var c) = GetTallies();

var of () 밖에있는 경우보다 짧은 구문을 사용할 수도 있습니다.

var (s, c) = GetTallies();

기존 변수를 해체 할 수도 있습니다.

int s, c;
(s, c) = GetTallies();

스와핑이 훨씬 간단 해졌습니다 (임시 변수가 필요 없음).

(b, a) = (a, b);

흥미롭게도 클래스 내의 Deconstruct 메소드를 정의하여 객체를 해체 할 수 있습니다.

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;

이 경우, (localFirstName, localLastName) = person 구문이 해당 person 에 대해 Deconstruct 를 호출합니다.

해체는 확장 방법으로 정의 할 수도 있습니다. 이것은 위와 같습니다.

public static class PersonExtensions
{
    public static void Deconstruct(this Person person, out string firstName, out string lastName)
    {
        firstName = person.FirstName;
        lastName = person.LastName;
    }
}

var (localFirstName, localLastName) = person;

Person 클래스의 또 다른 접근법은 Name 자체를 Tuple 로 정의하는 것입니다. 다음을 고려하세요:

class Person
{
    public (string First, string Last) Name { get; }

    public Person((string FirstName, string LastName) name)
    {
        Name = name;
    }
}

그런 다음 사람을 인스턴스화 할 수 있습니다 (여기서 인수로 튜플을 취할 수 있습니다).

var person = new Person(("Jane", "Smith"));

var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last;   // "Smith"

튜플 초기화

임의로 코드에서 튜플을 만들 수도 있습니다.

var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John

Console.WriteLine(name.Item2);
// Outputs Smith

튜플을 생성 할 때 튜플 멤버에 임시 항목 이름을 할당 할 수 있습니다.

var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John

유형 유추

동일한 서명 (일치하는 유형 및 개수)으로 정의 된 여러 튜플은 일치 유형으로 추정됩니다. 예 :

public (int sum, double average) Measure(List<int> items)
{
    var stats = (sum: 0, average: 0d);
    stats.sum = items.Sum();
    stats.average = items.Average();
    return stats;
}

stats 하여 선언 이후 반환 될 수있는 stats 변수와 메소드의 반환 서명이 일치합니다.

반사 및 튜플 필드 이름

런타임에는 구성원 이름이 없습니다. 리플렉션은 멤버 이름이 일치하지 않아도 동일한 수와 유형의 멤버가 동일한 튜플을 고려합니다. 튜플을 object 변환 한 다음 동일한 멤버 유형이지만 이름이 다른 튜플로 변환해도 예외가 발생하지 않습니다.

ValueTuple 클래스 자체가 멤버 이름에 대한 정보를 보존하지는 않지만 TupleElementNamesAttribute에서 리플렉션을 통해 정보를 사용할 수 있습니다. 이 속성은 튜플 자체에 적용되는 것이 아니라 메서드 매개 변수, 반환 값, 속성 및 필드에 적용됩니다. 이렇게하면 튜플 항목 이름을 어셈블리 전체에서 보존 할 수 있습니다. 즉, 메서드가 (문자열 이름, int 개수)를 반환하면 반환 값이 값을 포함하는 TupleElementNameAttribute로 표시되기 때문에 다른 어셈블리의 메서드 호출자가 이름과 개수를 사용할 수 있습니다. "이름"과 "계산".

제네릭 및 async 와 함께 사용

새로운 튜플 기능 (기본 ValueTuple 유형 사용)은 제네릭을 완벽하게 지원하며 제네릭 형식 매개 변수로 사용할 수 있습니다. 이렇게하면 async / await 패턴과 함께 사용할 수 await .

public async Task<(string value, int count)> GetValueAsync()
{
    string fooBar = await _stackoverflow.GetStringAsync();
    int num = await _stackoverflow.GetIntAsync();

    return (fooBar, num);
}

컬렉션과 함께 사용

코드 분기를 피하기 위해 조건에 ​​따라 일치하는 튜플을 찾으려는 시나리오에서 (예를 들어) 튜플 모음을 갖는 것이 유용 할 수 있습니다.

예:

private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
    new Tuple<string, string, string>("test1", "test2", "Value"),
    new Tuple<string, string, string>("test1", "test1", "Value2"),
    new Tuple<string, string, string>("test2", "test2", "Value3"),
};

public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
        .FirstOrDefault();

    if (result == null)
        throw new ArgumentException("combo not found");

    return result.Item3;
}

새로운 튜플은 다음과 같이 될 수 있습니다.

private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
    ("test1", "test2", "Value"),
    ("test1", "test1", "Value2"),
    ("test2", "test2", "Value3"),
}

public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
        .FirstOrDefault();

    if (result == null)
        throw new ArgumentException("combo not found");

    return result.foundValue;
}

위의 예제 튜플에 대한 명명법은 매우 일반적이지만 관련 레이블 개념을 사용하면 "item1", "item2"및 "item3"을 참조하는 코드에서 시도되는 내용을 더 깊이 이해할 수 있습니다.

ValueTuple과 Tuple의 차이점

ValueTuple 을 도입 한 주된 이유는 성과입니다.

유형 이름 ValueTuple Tuple
클래스 또는 구조체 struct class
변형 (생성 후 값 변경) 변하기 쉬운 불변의
이름 지정 구성원 및 기타 언어 지원 아니오 ( 미정 )

참고 문헌

로컬 함수

로컬 함수는 메서드 내에서 정의되며 외부에서 사용할 수 없습니다. 그들은 모든 지역 변수에 액세스 할 수 있고 iterators, async / await 및 lambda 구문을 지원합니다. 이 방법으로, 함수에 특정한 반복은 클래스를 군집하지 않고 기능화 될 수 있습니다. 부작용으로 인텔리 센스 제안 성능이 향상됩니다.

double GetCylinderVolume(double radius, double height)
{
    return getVolume();

    double getVolume()
    {
        // You can declare inner-local functions in a local function 
        double GetCircleArea(double r) => Math.PI * r * r;

        // ALL parents' variables are accessible even though parent doesn't have any input. 
        return GetCircleArea(radius) * height;
    }
}

지역 함수는 LINQ 연산자의 코드를 상당히 단순화합니다. 일반적으로 인수 검사를 실제 논리와 분리하여 반복 검사가 시작될 때까지 인수 검사를 즉시 수행해야합니다.

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (predicate == null) throw new ArgumentNullException(nameof(predicate));

    return iterator();

    IEnumerable<TSource> iterator()
    {
        foreach (TSource element in source)
            if (predicate(element))
                yield return element;
    }
}

로컬 함수는 asyncawait 키워드도 지원합니다.

async Task WriteEmailsAsync()
{
    var emailRegex = new Regex(@"(?i)[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z0-9-.]+");
    IEnumerable<string> emails1 = await getEmailsFromFileAsync("input1.txt");
    IEnumerable<string> emails2 = await getEmailsFromFileAsync("input2.txt");
    await writeLinesToFileAsync(emails1.Concat(emails2), "output.txt");

    async Task<IEnumerable<string>> getEmailsFromFileAsync(string fileName)
    {
        string text;

        using (StreamReader reader = File.OpenText(fileName))
        {
            text = await reader.ReadToEndAsync();
        }

        return from Match emailMatch in emailRegex.Matches(text) select emailMatch.Value;
    }

    async Task writeLinesToFileAsync(IEnumerable<string> lines, string fileName)
    {
        using (StreamWriter writer = File.CreateText(fileName))
        {
            foreach (string line in lines)
            {
                await writer.WriteLineAsync(line);
            }
        }
    }
}

로컬 함수가 return 문 아래에 정의 될 수 있다는 것만 큼 중요한 점은 정의 할 필요가 없다는 것입니다. 또한 로컬 함수는 일반적으로 "lowerCamelCase"명명 규칙을 따르므로 클래스 범위 기능과 쉽게 구분됩니다.

패턴 매칭

C #의 패턴 일치 확장은 기능 언어에서 패턴 매칭의 많은 이점을 얻을 수 있지만 기본 언어의 느낌과 원활하게 통합되는 방식으로

switch

패턴 일치는 switch 문을 확장하여 유형을 전환합니다.

class Geometry {} 

class Triangle : Geometry
{
    public int Width { get; set; }
    public int Height { get; set; }
    public int Base { get; set; }
}

class Rectangle : Geometry
{
    public int Width { get; set; }
    public int Height { get; set; }
}

class Square : Geometry
{
    public int Width { get; set; }
}

public static void PatternMatching()
{
    Geometry g = new Square { Width = 5 }; 
    
    switch (g)
    {
        case Triangle t:
            Console.WriteLine($"{t.Width} {t.Height} {t.Base}");
            break;
        case Rectangle sq when sq.Width == sq.Height:
            Console.WriteLine($"Square rectangle: {sq.Width} {sq.Height}");
            break;
        case Rectangle r:
            Console.WriteLine($"{r.Width} {r.Height}");
            break;
        case Square s:
            Console.WriteLine($"{s.Width}");
            break;
        default:
            Console.WriteLine("<other>");
            break;
    }
}

is 표현

패턴 일치는 is 연산자를 확장하여 유형을 확인하고 동시에 새 변수를 선언합니다.

7.0
string s = o as string;
if(s != null)
{
    // do something with s
}

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

7.0
if(o is string s)
{
    //Do something with s
};

또한 패턴 변수 s 의 범위가 if 블록의 바깥 쪽 범위로 확장되어 둘러싼 범위의 끝에 도달합니다 (예 :

if(someCondition)
{
   if(o is string s)
   {
      //Do something with s
   }
   else
   {
     // s is unassigned here, but accessible 
   }

   // s is unassigned here, but accessible 
}
// s is not accessible here

ref return 및 ref local

Ref 반환 및 참조 지역은 안전하지 않은 포인터를 사용하지 않고 메모리를 복사하는 대신 메모리 블록에 대한 참조를 조작하고 반환하는 데 유용합니다.

참조 반품

public static ref TValue Choose<TValue>(
    Func<bool> condition, ref TValue left, ref TValue right)
{
    return condition() ? ref left : ref right;
}

이것을 사용하면 참조로 두 개의 값을 전달할 수 있으며 그 중 하나는 어떤 조건에 따라 반환됩니다.

Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;

참조 지역

public static ref int Max(ref int first, ref int second, ref int third)
{
    ref int max = first > second ? ref first : ref second;
    return max > third ? ref max : ref third;
}
…
int a = 1, b = 2, c = 3;
Max(ref a, ref b, ref c) = 4;
Debug.Assert(a == 1); // true
Debug.Assert(b == 2); // true
Debug.Assert(c == 4); // true

안전하지 않은 참조 작업

System.Runtime.CompilerServices.Unsafe 에서는 기본적으로 포인터 인 것처럼 ref 값을 조작 할 수있는 안전하지 않은 작업이 정의되어 있습니다.

예를 들어, 메모리 주소 ( ref )를 다른 유형으로 재 해석하면 다음과 같습니다.

byte[] b = new byte[4] { 0x42, 0x42, 0x42, 0x42 };

ref int r = ref Unsafe.As<byte, int>(ref b[0]);
Assert.Equal(0x42424242, r);

0x0EF00EF0;
Assert.Equal(0xFE, b[0] | b[1] | b[2] | b[3]);

그러나 이것을 수행 할 때 엔디 BitConverter.IsLittleEndian 조심하십시오. 예를 들어 필요한 경우 BitConverter.IsLittleEndian 확인하고 그에 따라 처리하십시오.

또는 안전하지 않은 방식으로 배열을 반복합니다.

int[] a = new int[] { 0x123, 0x234, 0x345, 0x456 };

ref int r1 = ref Unsafe.Add(ref a[0], 1);
Assert.Equal(0x234, r1);

ref int r2 = ref Unsafe.Add(ref r1, 2);
Assert.Equal(0x456, r2);

ref int r3 = ref Unsafe.Add(ref r2, -3);
Assert.Equal(0x123, r3);

또는 이와 유사한 Subtract :

string[] a = new string[] { "abc", "def", "ghi", "jkl" };

ref string r1 = ref Unsafe.Subtract(ref a[0], -2);
Assert.Equal("ghi", r1);

ref string r2 = ref Unsafe.Subtract(ref r1, -1);
Assert.Equal("jkl", r2);

ref string r3 = ref Unsafe.Subtract(ref r2, 3);
Assert.Equal("abc", r3);

또한 두 개의 ref 값이 같은지 즉 동일한 주소인지 확인할 수 있습니다.

long[] a = new long[2];

Assert.True(Unsafe.AreSame(ref a[0], ref a[0]));
Assert.False(Unsafe.AreSame(ref a[0], ref a[1]));

모래밭

Roslyn Github Issue

github에 대한 System.Runtime.CompilerServices.Unsafe

표현을 던지다

C # 7.0에서는 특정 위치에서 표현식으로 throw 할 수 있습니다.

class Person
{
    public string Name { get; }

    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

    public string GetFirstName()
    {
        var parts = Name.Split(' ');
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }

    public string GetLastName() => throw new NotImplementedException();
}

C # 7.0 이전에는 식 본문에서 예외를 throw하려면 다음을 수행해야합니다.

var spoons = "dinner,desert,soup".Split(',');

var spoonsArray = spoons.Length > 0 ? spoons : null;

if (spoonsArray == null) 
{
    throw new Exception("There are no spoons");
}

또는

var spoonsArray = spoons.Length > 0 
    ? spoons 
    : new Func<string[]>(() => 
      {
          throw new Exception("There are no spoons");
      })();

C # 7.0에서 위의 내용은 다음과 같이 단순화되었습니다.

var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");

확장 된 표현식 본문 멤버 목록

C # 7.0에서는 표현식 본문을 가질 수있는 항목 목록에 접근 자, 생성자 및 종료자를 추가합니다.

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();

    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructors

    ~Person() => names.TryRemove(id, out _);              // finalizers

    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}

또한 파기 연산자에 대한 out var 선언 섹션을 참조하십시오.

ValueTask

Task<T>클래스 이므로 결과를 즉시 사용할 수있을 때 할당의 불필요한 오버 헤드가 발생합니다.

ValueTask<T>구조 이며 비동기 작업의 결과가 대기 시간에 이미 사용 가능한 경우 Task 개체 할당을 방지하기 위해 도입되었습니다.

따라서 ValueTask<T> 는 두 가지 이점을 제공합니다.

1. 성능 향상

다음은 Task<T> 예제입니다.

  • 힙 할당 필요
  • JIT와 120ns 소요
async Task<int> TestTask(int d)
{
    await Task.Delay(d);
    return 10;
}

다음은 아날로그 ValueTask<T> 예제입니다.

  • 결과가 동 기적으로 알려진 경우 어떤 힙 할당 (이 때문에이 경우에하지 않은 Task.Delay 종종 있지만, 많은 현실 세계에없는 async / await 시나리오)
  • JIT와 함께 65ns 소요
async ValueTask<int> TestValueTask(int d)
{
    await Task.Delay(d);
    return 10;
}

2. 구현 유연성 향상

동기를 원하는 비동기 인터페이스의 구현은 달리 Task.Run 또는 Task.FromResult 를 사용하도록 강제 될 수 있습니다 (위에서 설명한 성능 저하의 결과로). 따라서 동기 구현에 대한 압력이 있습니다.

그러나 ValueTask<T> 하면 구현자가 호출자에게 영향을주지 않고 동기식 또는 비동기식 중에서 자유롭게 선택할 수 있습니다.

예를 들어 비동기 메서드가있는 인터페이스는 다음과 같습니다.

interface IFoo<T>
{
    ValueTask<T> BarAsync();
}

...이 메소드가 호출 될 수있는 방법은 다음과 같습니다.

IFoo<T> thing = getThing();
var x = await thing.BarAsync();

ValueTask 를 사용하면 위의 코드가 동기식 또는 비동기식 구현에서 작동합니다 .

동기 구현 :

class SynchronousFoo<T> : IFoo<T>
{
    public ValueTask<T> BarAsync()
    {
        var value = default(T);
        return new ValueTask<T>(value);
    }
}

비동기 구현

class AsynchronousFoo<T> : IFoo<T>
{
    public async ValueTask<T> BarAsync()
    {
        var value = default(T);
        await Task.Delay(1);
        return value;
    }
}

노트

ValueTask 구조체가 C # 7.0에 추가 될 예정 이었지만, 당분간은 다른 라이브러리로 유지되었습니다. ValueTask <T> System.Threading.Tasks.Extensions 패키지는 Nuget Gallery 에서 다운로드 할 수 있습니다.



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