C# Language
C # 7.0 기능
수색…
소개
C # 7.0은 C #의 7 번째 버전입니다. 이 버전에는 튜플에 대한 언어 지원, 로컬 함수, out var
선언, 숫자 구분 기호, 바이너리 리터럴, 패턴 일치, 표현식 ref return
, ref return
및 ref 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
사용하면 함수를 호출하기 전에 값을받을 변수를 선언해야합니다.
int value;
if (int.TryParse(input, out value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // ok
C # 7.0에서는 out
매개 변수에 전달 된 변수의 선언을 인라인 할 수 있으므로 별도의 변수 선언이 필요 없습니다.
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
선언의 또 다른 특징은 익명 타입과 함께 사용할 수 있다는 것입니다.
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;
}
}
로컬 함수는 async
및 await
키워드도 지원합니다.
예
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
연산자를 확장하여 유형을 확인하고 동시에 새 변수를 선언합니다.
예
string s = o as string;
if(s != null)
{
// do something with s
}
다음과 같이 다시 쓸 수 있습니다 :
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]));
모래밭
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 에서 다운로드 할 수 있습니다.