튜플 및 기타 형식 분해
튜플은 메서드 호출에서 여러 값을 검색할 수 있는 간단한 방법을 제공합니다. 하지만 튜플을 검색한 후 튜플의 개별 요소를 처리해야 합니다. 다음 예에서 볼 수 있듯이 요소별로 작업하는 것은 번거롭습니다.
QueryCityData
메서드는 3개의 튜플을 반환하고 각 요소는 별도의 작업을 통해 변수에 할당됩니다.
public class Example
{
public static void Main()
{
var result = QueryCityData("New York City");
var city = result.Item1;
var pop = result.Item2;
var size = result.Item3;
// Do something with the data.
}
private static (string, int, double) QueryCityData(string name)
{
if (name == "New York City")
return (name, 8175133, 468.48);
return ("", 0, 0);
}
}
개체에서 여러 필드 및 속성 값을 검색하는 작업도 똑같이 번거로울 수 있습니다. 멤버별로 변수에 필드 또는 속성 값을 할당해야 하기 때문입니다.
단일 분해 작업으로 튜플에서 여러 요소를 검색하거나 개체에서 여러 필드, 속성 및 계산된 값을 검색할 수 있습니다. 튜플을 분해하려면 해당 요소를 개별 변수에 할당합니다. 개체를 분해할 때는 선택한 값을 개별 변수에 할당합니다.
튜플
C#에서는 튜플 분해를 기본적으로 지원하므로 한 작업에서 튜플의 모든 항목을 패키지 해제할 수 있습니다. 튜플을 분해하는 일반 구문은 정의하는 구문과 유사합니다. 즉, 대입문 왼쪽에서 각 요소가 할당되는 변수를 괄호로 묶습니다. 예를 들어, 다음 문은 4-튜플의 요소를 4개의 개별 변수에 할당합니다.
var (name, address, city, zip) = contact.GetAddressInfo();
다음과 같은 세 가지 방법으로 튜플을 분해합니다.
괄호 안에 각 필드의 형식을 명시적으로 선언할 수 있습니다. 다음 예에서는 이 방법을 사용하여
QueryCityData
메서드에서 반환된 3-튜플을 분해합니다.public static void Main() { (string city, int population, double area) = QueryCityData("New York City"); // Do something with the data. }
C#에서 각 변수의 형식을 유추하도록
var
키워드를 사용할 수 있습니다.var
키워드는 괄호 밖에 놓습니다. 다음 예에서는QueryCityData
메서드에서 반환된 3-튜플을 분해할 때 형식 유추를 사용합니다.public static void Main() { var (city, population, area) = QueryCityData("New York City"); // Do something with the data. }
괄호 안에 일부 또는 모든 변수 선언에
var
키워드를 개별적으로 사용할 수도 있습니다.public static void Main() { (string city, var population, var area) = QueryCityData("New York City"); // Do something with the data. }
앞의 예제는 번거롭고 권장되지 않습니다.
마지막으로 튜플을 이미 선언된 변수로 분해할 수 있습니다.
public static void Main() { string city = "Raleigh"; int population = 458880; double area = 144.8; (city, population, area) = QueryCityData("New York City"); // Do something with the data. }
분해에서 변수 선언과 할당을 혼합할 수 있습니다.
public static void Main() { string city = "Raleigh"; int population = 458880; (city, population, double area) = QueryCityData("New York City"); // Do something with the data. }
튜플의 모든 필드가 동일한 형식을 가지더라도 괄호 외부에 특정 형식을 지정할 수 없습니다. 이렇게 하면 컴파일러 오류 CS8136이 생성됩니다. "분해 'var (...)' 양식은 'var'에 대한 특정 형식을 허용하지 않습니다."
튜플의 각 요소를 변수에 할당해야 합니다. 요소를 생략하면 컴파일러에서 오류 CS8132, "'x' 요소의 튜플을 'y' 변수로 분해할 수 없습니다."를 생성합니다.
무시 항목이 있는 튜플 요소
튜플을 분해할 때 일부 요소 값에만 관심이 있는 경우가 종종 있습니다. C#의 지원을 통해, 값을 무시하도록 선택할 수 있는 쓰기 전용 변수인_
는 모든 삭제된 값을 나타내는 단일 삭제입니다.
다음 예제에서는 무시 항목과 함께 튜플을 사용하는 방법을 보여 줍니다.
QueryCityDataForYears
메서드는 도시 이름, 지역, 연도, 해당 연도의 도시 모집단, 두 번째 해, 두 번째 해의 도시 모집단이 포함된 6개의 튜플을 반환합니다. 이 예제는 이러한 두 연도 사이의 인구 변화를 보여 줍니다. 튜플에서 사용 가능한 데이터 중 도시 면적에는 관심이 없고 디자인 타임에 도시 이름과 두 날짜를 알고 있습니다. 따라서 튜플에 저장된 두 가지 인구 값에만 관심이 있고 나머지 값은 무시 항목으로 처리할 수 있습니다.
using System;
public class ExampleDiscard
{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
}
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
사용자 정의 형식
C#은 튜플 형식과 record
, DictionaryEntry 형식을 분해할 수 있는 기본 지원을 제공합니다. 그러나 클래스, 구조체 또는 인터페이스의 만든 이는 하나 이상의 Deconstruct
메서드를 구현하여 형식의 인스턴스를 분해하도록 허용할 수 있습니다. 메서드는 void를 반환합니다. 메서드 시그니처의 out 매개 변수는 분해할 각 값을 나타냅니다. 예를 들어 Person
클래스의 다음 Deconstruct
메서드는 첫 번째, 중간 및 패밀리 이름을 반환합니다.
public void Deconstruct(out string fname, out string mname, out string lname)
그리고 다음 코드와 같은 할당을 사용하여 Person
라는 p
클래스의 인스턴스를 분해할 수 있습니다.
var (fName, mName, lName) = p;
다음 예제에서는 Deconstruct
메서드를 오버로드하여 Person
개체의 속성을 다양한 조합으로 반환합니다. 개별 오버로드는 다음을 반환합니다.
- 성과 이름입니다.
- 첫 번째, 중간 및 가족 이름입니다.
- 이름, 가족 이름, 도시 이름 및 주 이름입니다.
using System;
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person(string fname, string mname, string lname,
string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}
// Return the first and last name.
public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}
public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
public void Deconstruct(out string fname, out string lname,
out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}
public class ExampleClassDeconstruction
{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");
// Deconstruct the person object.
var (fName, lName, city, state) = p;
Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
}
}
// The example displays the following output:
// Hello John Adams of Boston, MA!
매개 변수 수가 같은 여러 Deconstruct
메서드는 모호합니다. 매개 변수 수, 즉 "인자"가 다른 Deconstruct
메서드를 정의하도록 주의해야 합니다. 매개 변수 수가 동일한 Deconstruct
메서드는 오버로드 확인 중에 구별할 수 없습니다.
무시 항목이 포함된 사용자 정의 형식
튜플에서와 마찬가지로 무시 항목을 사용하여 Deconstruct
메서드에서 반환된 항목 중 선택한 항목을 무시할 수 있습니다. "_"라는 변수는 무시를 나타냅니다. 단일 분해 작업에는 여러 폐기가 포함될 수 있습니다.
다음 예제에서는 Person
개체를 네 개의 문자열(이름 및 패밀리 이름, 도시 및 주)으로 분해하지만 패밀리 이름과 상태를 삭제합니다.
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
분해 확장 메서드
클래스, 구조체 또는 인터페이스의 만든 이가 아니더라도 하나 이상의 Deconstruct
확장 메서드 구현을 통해 해당 형식의 개체를 분해하여 관심 있는 값을 반환할 수 있습니다.
다음 예제에서는 Deconstruct
클래스에 대한 두 개의 System.Reflection.PropertyInfo 확장 메서드를 정의합니다. 첫 번째는 속성의 특성을 나타내는 값 집합을 반환합니다. 두 번째 메서드는 속성의 접근성을 나타냅니다. 부울 값은 속성에 별도의 get 및 set 접근자 또는 다른 접근성이 있는지 여부를 나타냅니다. 접근자가 하나만 있거나 get 및 set 접근자 모두 동일한 접근성을 갖는 경우 access
변수는 속성의 접근성을 전체적으로 나타냅니다. 그러지 않으면 get 및 set 접근자의 접근성이 getAccess
및 setAccess
변수로 표시됩니다.
using System;
using System.Collections.Generic;
using System.Reflection;
public static class ReflectionExtensions
{
public static void Deconstruct(this PropertyInfo p, out bool isStatic,
out bool isReadOnly, out bool isIndexed,
out Type propertyType)
{
var getter = p.GetMethod;
// Is the property read-only?
isReadOnly = ! p.CanWrite;
// Is the property instance or static?
isStatic = getter.IsStatic;
// Is the property indexed?
isIndexed = p.GetIndexParameters().Length > 0;
// Get the property type.
propertyType = p.PropertyType;
}
public static void Deconstruct(this PropertyInfo p, out bool hasGetAndSet,
out bool sameAccess, out string access,
out string getAccess, out string setAccess)
{
hasGetAndSet = sameAccess = false;
string getAccessTemp = null;
string setAccessTemp = null;
MethodInfo getter = null;
if (p.CanRead)
getter = p.GetMethod;
MethodInfo setter = null;
if (p.CanWrite)
setter = p.SetMethod;
if (setter != null && getter != null)
hasGetAndSet = true;
if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}
if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}
// Are the accessibility of the getter and setter the same?
if (setAccessTemp == getAccessTemp)
{
sameAccess = true;
access = getAccessTemp;
getAccess = setAccess = String.Empty;
}
else
{
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
}
}
}
public class ExampleExtension
{
public static void Main()
{
Type dateType = typeof(DateTime);
PropertyInfo prop = dateType.GetProperty("Now");
var (isStatic, isRO, isIndexed, propType) = prop;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name} property:");
Console.WriteLine($" PropertyType: {propType.Name}");
Console.WriteLine($" Static: {isStatic}");
Console.WriteLine($" Read-only: {isRO}");
Console.WriteLine($" Indexed: {isIndexed}");
Type listType = typeof(List<>);
prop = listType.GetProperty("Item",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
var (hasGetAndSet, sameAccess, accessibility, getAccessibility, setAccessibility) = prop;
Console.Write($"\nAccessibility of the {listType.FullName}.{prop.Name} property: ");
if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item property: public
시스템 형식의 확장 메서드
일부 시스템 형식은 편의를 위해 Deconstruct
메서드를 제공합니다. 예를 들어, System.Collections.Generic.KeyValuePair<TKey,TValue> 형식은 이 기능을 제공합니다.
System.Collections.Generic.Dictionary<TKey,TValue>를 반복할 때, 각 요소는 KeyValuePair<TKey, TValue>
로 분해될 수 있습니다. 다음 예제를 참조하세요.
Dictionary<string, int> snapshotCommitMap = new(StringComparer.OrdinalIgnoreCase)
{
["https://github.com/dotnet/docs"] = 16_465,
["https://github.com/dotnet/runtime"] = 114_223,
["https://github.com/dotnet/installer"] = 22_436,
["https://github.com/dotnet/roslyn"] = 79_484,
["https://github.com/dotnet/aspnetcore"] = 48_386
};
foreach (var (repo, commitCount) in snapshotCommitMap)
{
Console.WriteLine(
$"The {repo} repository had {commitCount:N0} commits as of November 10th, 2021.");
}
record
형식
둘 이상의 위치 매개 변수를 사용하여 record 형식을 선언하는 경우 컴파일러는 Deconstruct
선언의 각 위치 매개 변수에 대해 out
매개 변수를 사용하여 record
메서드를 만듭니다. 자세한 내용은 속성 정의의 위치 구문 및 파생 레코드의 분해자 동작을 참조하세요.
참고 항목
.NET