<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SJG 블로그</title>
    <link>https://thddlwnsrb.tistory.com/</link>
    <description>공부한 것을 정리해 놓은 블로그</description>
    <language>ko</language>
    <pubDate>Sat, 11 Apr 2026 03:22:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>월러비</managingEditor>
    <item>
      <title>아이템 슬롯, 스크롤 뷰, 슬롯 데이터 저장, 세이브 로드, 인벤토리 정렬, 인벤토리 필터링, 드롭다운</title>
      <link>https://thddlwnsrb.tistory.com/244</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI는 계층적으로 만들어야한다. &amp;rarr; 재사용이 가능하면 좋다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장비 슬롯 같은 경우 &amp;rarr; 부위별 슬롯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;먼저 인벤토리를 한다면 슬롯 하나를 만들고 함수를 만들고 테스트를 하고 된다면 그 다음으로 넘어가야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비어있는 상태&lt;/li&gt;
&lt;li&gt;아이템이 들어있는 상태&lt;/li&gt;
&lt;li&gt;클릭을 했을때의 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정보 표시&lt;/li&gt;
&lt;li&gt;클릭을 한 다음 삭제버튼을 누르면 삭제되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;아이템 슬롯&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬롯 프리펩 생성 - 빈상태 함수, 아이템 있는 상태 함수 틀 선언
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금은 아이템 정보를 한번에 받지만, 원래는 개별적인 아이템 정보를 받아야한다.&lt;/li&gt;
&lt;li&gt;언제 획득했냐, 랜덤으로 능력치가 붙는게 있지 않나 같은 정보를 넣어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빈상태 함수 : 아이템 데이터, 이미지, 이름 텍스트 비우기&lt;/li&gt;
&lt;li&gt;슬롯 채우기 함수 ; 아이템 데이터, 이미지, 이름 텍스트 채우기 &amp;rarr; 아이템 데이터로 할당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;//슬롯 비우기
public void SetEmpty()
{
    ItemData = null;
    imageIcon.sprite = null; //null이면 아무것도 안그려진다.
    textName.text = string.Empty;
}

//슬롯 채우기
public void SetItem(SaveItemData data)
{
    ItemData = data;
    imageIcon.sprite = ItemData.itemData.SpriteIcon;
    textName.text = ItemData.itemData.StringName;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스크롤 뷰&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Viewport안의 Content에 슬롯같은 요소들이 들어간다.&lt;/li&gt;
&lt;li&gt;아무리 많아도 viewport의 영역만큼만 보이게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요소를 드래그하면 스크롤바가 움직이게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스크립트에서 content 오브젝트의 참조하기 위해선 ScrollView 클래스가 아니라 ScrollRect 클래스로 객체를 선언한 다음에 content 프로퍼티를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public ScrollRect scrollRect;

public void AddRandomItem()
{
    var itemData = DataTableManager.ItemTable.GetRandom();
    var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
    newSlot.SetItem(itemData);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;content 레이아웃 배치 제한&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Add Component로 레이아웃 제한이 가능하다.&lt;/li&gt;
&lt;li&gt;Grid Layout Group : content안에 요소를 좌우 몇개까지 배치가능한지 정하는 컴포넌트다.&lt;/li&gt;
&lt;li&gt;Content Size Fitter : 레이아웃 요소의 크기를 제어하는 레이아웃 컨트롤러의 기능이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unconstrained : 레이아웃 요소에 기반하여 높이를 조정하지 않는다.&lt;/li&gt;
&lt;li&gt;Min Size : 최소 높이에 기반하여 높이를 조정한다.&lt;/li&gt;
&lt;li&gt;Preferred Size : 기본 높이에 기반하여 높이를 조정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스크롤 뷰 드래그 제한&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scroll View - Scroll Rect - Horizontal 또는 Vertical : 상하 또는 좌우 드래그 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;슬롯 데이터 저장&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고유한 아이디는 중복되면 안된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 있는게임은 아이디를 서버가 줘서 받아서 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고유 아이디 아이디어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;흘러가는 시간 &amp;rarr; 가장 속 편한 방법&lt;/li&gt;
&lt;li&gt;파일로 숫자 지정 &amp;rarr; 증가할때마다 1씩 증가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전제 : 서버가 없고 클라이언트만 존재할때 이 파일이 제대로 보존되어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;슬롯 변경 시 업데이트&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private void UpdateSlots(List&amp;lt;SaveItemData&amp;gt; itemList)
{
    if (slotList.Count &amp;lt; itemList.Count)
    {
        for (int i = slotList.Count; i &amp;lt; itemList.Count; ++i)
        {
            var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
            newSlot.slotIndex = i; //슬롯 번호
            newSlot.SetEmpty();
            newSlot.gameObject.SetActive(false);
            slotList.Add(newSlot);
        }
    }
    for (int i = 0; i &amp;lt; slotList.Count; ++i)
    {
        if (i &amp;lt; itemList.Count)
        {
            slotList[i].gameObject.SetActive(true);
            slotList[i].SetItem(itemList[i]);
        }
        else
        {
            slotList[i].SetEmpty();
            slotList[i].gameObject.SetActive(false);
        }
    }
}

public void AddRandomItem()
{
    //var itemData = DataTableManager.ItemTable.GetRandom();
    //testItemList.Add(itemData);

    var itemInstance = new SaveItemData();
    itemInstance.itemData = DataTableManager.ItemTable.GetRandom();
    testItemList.Add(itemInstance);
    UpdateSlots(testItemList);
}

public void RemoveItem(int slotIndex)
{
    testItemList.RemoveAt(slotIndex);
    UpdateSlots(testItemList);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ItemData : ItemTable에 포함되어있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ItemTable은 실제 빌드할때 이미 생성되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SaveItemData : 세이브 로드할때 세이브 파일에 포함될 데이터다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이템을 얻는 순간에 하나씩 만들어진다.&lt;/li&gt;
&lt;li&gt;얘네끼리도 서로 구분이 가능해야한다. &amp;rarr; ID가 중복되야하기 때문이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물약은 id가 하나지만 여러개 들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, 각 객체의 고유 id가 필요하기에 Guid(또는 DateTime)가 필요한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차이점 : ItemData &amp;rarr; 각 아이템의 정보다. / SaveItemData : 사용해야할 실제 아이템 정보
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사전의 Id가 세이브 아이템 정보와 공유하고있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, ItemData의 Id는 해당 아이템의 id이고, SaveItemData의 Id는 해당 아이템의 고유 Id이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Serializable]
public class SaveItemData
{
    public Guid instanceId; //고유 아이디 클래스를 사용할때 / 뉴튼 소프트는 이게 자동으로 직렬 역직렬이 되는 기능이 있다.

    [JsonConverter(typeof(ItemDataConverter))] //뉴튼Josn의 제이슨 컨버터 어트리뷰트다.
    public ItemData itemData; //직렬화하기 위해서 JsonConveter를 만들어야한다.

    public DateTime creationTime; //시간으로 아이디를 만들때

    public SaveItemData() 
    {
        instanceId = Guid.NewGuid(); //새로운 고유 아이디 반환 함수
        creationTime = DateTime.Now; //현재시간
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;세이브 로드&lt;/h1&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void Save()
{
    var jsontext = JsonConvert.SerializeObject(testItemList);
    var filePath = Path.Combine(Application.persistentDataPath, &quot;test.json&quot;);
    File.WriteAllText(filePath, jsontext);
}
public void Load()
{
    var filePath = Path.Combine(Application.persistentDataPath, &quot;test.json&quot;);
    if (!File.Exists(filePath))
    {
        return;
    }

    var jsonText = File.ReadAllText(filePath);
    testItemList = JsonConvert.DeserializeObject&amp;lt;List&amp;lt;SaveItemData&amp;gt;&amp;gt;(jsonText);

    UpdateSlots(testItemList);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장 후 종료 및 불러오기&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;private void OnEnable()
{
    Load();
}

private void OnDisable()
{
    Save();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;인벤토리 정렬&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬없고 필터링 없는 상태, 정렬 상태, 필터 상태 3개가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public readonly System.Comparison&amp;lt;SaveItemData&amp;gt;[] comparisons =
{
    (lhs, rhs) =&amp;gt; lhs.creationTime.CompareTo(rhs.creationTime),
    (lhs, rhs) =&amp;gt; lhs.creationTime.CompareTo(lhs.creationTime),
    (lhs, rhs) =&amp;gt; lhs.itemData.StringName.CompareTo(rhs.itemData.StringName),
    (lhs, rhs) =&amp;gt; lhs.itemData.StringName.CompareTo(lhs.itemData.StringName),
    (lhs, rhs) =&amp;gt; lhs.itemData.Cost.CompareTo(rhs.itemData.Cost),
    (lhs, rhs) =&amp;gt; lhs.itemData.Cost.CompareTo(lhs.itemData.Cost),
}; //정렬

private SortingOptions sorting = SortingOptions.NameAccending; //정렬 비교 객체
public SortingOptions Sorting
{
    get =&amp;gt; sorting;
    set
    {
        //1. 필터링된 리스트 가져오기 / 2. 정렬 실행 / 3. 슬롯 업데이트
        sorting = value;
        UpdateSlots(testItemList);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인벤토리 필터링&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;public readonly System.Func&amp;lt;SaveItemData, bool&amp;gt;[] filterings =
{
    x =&amp;gt; true,
    x =&amp;gt; x.itemData.Type == ItemTypes.Weapon,
    x =&amp;gt; x.itemData.Type == ItemTypes.Equip,
    x =&amp;gt; x.itemData.Type == ItemTypes.Consumable,
}; //필터링

private FilteringOptions filtering = FilteringOptions.None; //필터링 비교 객체
public FilteringOptions Filtering
{
    get =&amp;gt; filtering;
    set
    {
        filtering = value;
        UpdateSlots(testItemList);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;드롭다운&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬 또는 필터링같은 특정 항목에 따라서 기능이 달라지는 상황에 사용한다.&lt;/li&gt;
&lt;li&gt;OnChanged 항목에 함수를 연결하면 드롭다운에서 선택한 항목의 index가 함수의 매개변수로 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757411825818&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void OnChangeSorting(int index)
{
    slotList.Sorting = (UiInvenSlotList.SortingOptions)index;
}

public void OnChangeFiltering(int index)
{
    slotList.Filtering = (UiInvenSlotList.FilteringOptions)index;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부/Unity</category>
      <category>드롭다운</category>
      <category>세이브 로드</category>
      <category>스크롤 뷰</category>
      <category>슬롯 데이터 저장</category>
      <category>아이템 슬롯</category>
      <category>인벤토리 정렬</category>
      <category>인벤토리 필터링</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/244</guid>
      <comments>https://thddlwnsrb.tistory.com/244#entry244comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:57:23 +0900</pubDate>
    </item>
    <item>
      <title>NuGetForUnity, CSVHelper, Resources, 에디터 커스터마이징</title>
      <link>https://thddlwnsrb.tistory.com/243</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리공간을 구성하는 객체의 요소 &amp;rarr; 필드&lt;/li&gt;
&lt;li&gt;외부 라이브러리로 직렬화를 하기 위해서는 명시적으로 직렬화라는것을 알려야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[Serializable] : 직렬화 어트리뷰트&lt;/li&gt;
&lt;li&gt;private 접근도 직렬화가 되지 않는다.&lt;/li&gt;
&lt;li&gt;직렬화가 되지 않는다면 해당 작업이 스킵되는 것이다.&lt;/li&gt;
&lt;li&gt;[NonSerialized]과의 차이점 : 이것은 직렬화되는 클래스에서 특정 필드를 직렬화시키고싶지 않을때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Serializable]
public class SaveDatas
{
    public Vector3 position;
    public Quaternion rotation;
    public Vector3 scale;
    
    [NonSerialized]
    public Color color;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[HideInInspector] : 인스펙터에서 가려진다.&lt;/li&gt;
&lt;li&gt;[SerializeField] : private여도 직렬화가 되어 인스펙터에 노출되는 어트리뷰트다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 지금은 연습이기에 public으로 작성중이지만, 현업에서는 private로 선언하고 작성해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부 라이브러리는 Vector같은 구조를 직렬화하지 못한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 경우는 JsonConverter를 &amp;lsquo;재정의&amp;rsquo;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Load로 오브젝트를 추가하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 정보에 추가하는 경우&lt;/li&gt;
&lt;li&gt;기존의 정보를 싹 초기화하고 추가하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인스펙터에 public으로 할당된 데이터는 어디에 저장될까? : 씬 파일(에셋)에 기록되어있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 이것도 직렬화 역 직렬화다.&lt;/li&gt;
&lt;li&gt;Json 직렬화 역직렬화처럼, 해당 씬에서 작업한 내용들(오브젝트 생성, 인스펙터 값 조정 등)이 해당 씬 파일에 직렬화되어서 저장되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;b&gt;NuGetForUnity&lt;/b&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라이브러리 연결을 편하게 해주는 툴이다.&lt;/li&gt;
&lt;li&gt;외부 라이브러리에 연결된 또 다른 라이브러리가 있을 때 그것을 또 다운받아서 연결해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또, 다운받은 라이브러리가 호환이 안되는 경우가 생길 수 있다.&lt;/li&gt;
&lt;li&gt;이러한 문제들을 해결해주는 패키지가 &amp;lsquo;누겟 라이브러리&amp;rsquo;다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GlitchEnzo/NuGetForUnity&quot;&gt;https://github.com/GlitchEnzo/NuGetForUnity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;깃허브 사이트에서 &lt;b&gt;How do I install NuGetForUnity 항목 확인&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Package Manager를 이용하는 2번째 방법 확인&lt;/li&gt;
&lt;li&gt;해당 링크 복사 후 유니티 Package Manager에서 Install by URL로 다운로드&lt;/li&gt;
&lt;li&gt;상단에 Nuget 항목이 생긴다.&lt;/li&gt;
&lt;li&gt;만약 안된다면?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깃허브 홈페이지에 릴리즈가 있다.&lt;/li&gt;
&lt;li&gt;그곳에 UnityPackgae 파일이 있으니 그것을 에셋 임포트 하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nuget &amp;rarr; Manage Nuget Packages : 누겟으로 설치할 수 있는 외부 라이브러리들을 확인할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NewtonJson도 이곳에서 다운로드할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;CSVHelper&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://joshclose.github.io/CsvHelper/&quot;&gt;https://joshclose.github.io/CsvHelper/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;csv와 관련된 기능이 모여진 외부 라이브러리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 이유&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이템 하나만 보더라도 아이디, 기능 등 여러 데이터들이 있고, 이 아이템은 하나가 아니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또, 아이템 뿐만이 아니라 여러 클래스의 데이터들이 매우 많다.&lt;/li&gt;
&lt;li&gt;이것을 하드코딩으로 짠다면? : 하나하나 찾으러 돌아다녀야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 테이블을 생성해야한다.&lt;/li&gt;
&lt;li&gt;데이터의 구별은 이름이 아니라 &amp;lsquo;ID&amp;rsquo;로 구분해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이디별 내용도 가서 수정해줘야하나 전부 바꾸기엔 좀 어렵다.&lt;/li&gt;
&lt;li&gt;되도록 하드코딩 없이만 작성하도록 해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;셀 하나마다 파싱해서 구조체처럼 사용이 가능하다.&lt;/li&gt;
&lt;li&gt;csv 파일을 엑셀로 사용하는것은 추천하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인코딩이 저장할때마다 바뀌어서 그렇다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장할때마다 계속 수동으로 수정해줘야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;vscode 또는 구글 스프레드 시트를 사용하는것을 추천한다.&lt;/li&gt;
&lt;li&gt;Edit CSV가 vscode의 확장프로그렘이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아예 메모리에 항상 놓고 빠르게 가져와서 사용하는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언어가 여러개면 조금 불필요한 면이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using CsvHelper;

 private void Start()
 {
     //Resources 폴더에 있는 에셋을 가져오는 코드
     TextAsset csv = Resources.Load&amp;lt;TextAsset&amp;gt;(&quot;DataTables/StringTable&quot;);
     //Resources.UnloadAsset(csv); //한번 로드된 에셋은 메모리에서 내려오지 않는다. / 즉, 언로드를 하지 않으면 계속 메모리에 남아있게 된다.

     //CultureInfo.InvariantCulture : 지역설정 무관하게 처리하는 옵션이다.
     using (var reader = new StringReader(csv.text))
     using(var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture))
     {
         var records = csvReader.GetRecords&amp;lt;CsvData&amp;gt;();
         foreach(var record in records)
         {
             Debug.Log($&quot;{record.Id} : {record.String}&quot;);
         }
     }

     //Debug.Log(csv.text);
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Resources&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 테이블들은 Json과는 달리 빌드에 포함시킬 데이터들이다.&lt;/li&gt;
&lt;li&gt;빌드하면 패킹되어서 나오기 때문에 Asset에 넣은 파일들의 경로가 맞지 않게된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Resources 폴더를 이용해서 경로(파일 이름)로 받는다.&lt;/li&gt;
&lt;li&gt;Addressable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유니티에서 약속한 이름이기 때문에 폴더를 사용한다면 이름을 틀리면 안된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 폴더에서도 리소스 폴더가 있다면 모든 리소스 폴더가 합쳐져서 가져오게 된다.&lt;/li&gt;
&lt;li&gt;문제 : 이름이 중복될 가능성이 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;겹치지 않게 수동으로 관리해줘야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CSV는 Text 에셋으로 유니티에 들어가게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;데이터 테이블 매니저&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요한 데이터 테이블을 가져오는 매니저를 생성해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;언어 변경&lt;/h1&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#if UNITY_EDITOR
    public Languages editorLang;
#endif
    
    //빌드할때 이렇게 하면 된다.
    private void OnEnable()
    {
#if UNITY_EDITOR
        if (Application.isPlaying)
        {
            OnChangeLanguage();
        }
        else
        {
            OnChangeLanguage(editorLang);
        }
#else
        OnChangeLanguage();
#endif
    }

    public void OnChangeLanguage()
    {
        var stringTable = DataTableManager.StringTable;
        text.text = stringTable.Get(stringId);
    }

#if UNITY_EDITOR
    public void OnChangeLanguage(Languages lang)
    {
        var tableId = DataTableIds.StringTableIds[(int)lang];
        var stringTable = DataTableManager.Get&amp;lt;StringTable&amp;gt;(tableId);
        text.text = stringTable.Get(stringId);
    }
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;에디터 커스터마이징&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인스펙터 public 오버라이드&lt;/li&gt;
&lt;li&gt;Editor 폴더 : Resources 폴더처럼 유니티에서 약속된 이름이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 이외에서 에디터를 바꾼다면 빌드 이후로 에러가 난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;CSV 내용 정리&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하드코딩 피하는 방법 : 외부파일 또는 서버를 통해 데이터를 전달받는다.&lt;/li&gt;
&lt;li&gt;데이터 테이블이란? : 서식을 정해서 키와 벨류를 작성하는 파일이다.&lt;/li&gt;
&lt;li&gt;CSV 포멧 파싱 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 만든다 &amp;rarr; 가능하지만 원하는 기능이 제대로 동작하지 않을 수 있다. (최적화 , 성능)&lt;/li&gt;
&lt;li&gt;오픈소스 라이브러리 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 라이브러리 사용방법
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;잘 만든 유니티 패키지 임포트(NetonJson)&lt;/li&gt;
&lt;li&gt;Nuget 패키지 매니저 사용
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;플러그인 설치가 필요하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 테이블 추상 클래스 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Load(string filename) :&lt;/li&gt;
&lt;li&gt;Load(strig csvText)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트를 받으면 stringReader에 넘기고 파싱된 값을 받는다.&lt;/li&gt;
&lt;li&gt;GetRecords : 키와 벨류 짝에 맞춰서 객체로 리턴한다.&lt;/li&gt;
&lt;li&gt;그것을 ToList로 리스트를 만들어 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;public static List&amp;lt;T&amp;gt; LoadCSV&amp;lt;T&amp;gt;(string csvText) { using (var reader = new StringReader(csvText)) using (var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csvReader.GetRecords&amp;lt;T&amp;gt;(); return records.ToList(); } }&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;StringTable&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 클래스 선언
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Id와 문자열을 저장한다. &amp;rarr; 키와 벨류&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;딕셔너리 선언 &amp;rarr; 키와 벨류 저장&lt;/li&gt;
&lt;li&gt;Load(string filename) : 딕셔너리에 키와 벨류를 저장하는 함수다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public override void Load(string fileame)
{
    dictionary.Clear(); //비어있는 상태로 만들고 새로 채우는 것이다.

    var path = string.Format(FormatPath, fileame); //파일 경로
    var textAsset = Resources.Load&amp;lt;TextAsset&amp;gt;(path);

    var list = LoadCSV&amp;lt;Data&amp;gt;(textAsset.text);
    foreach( var item in list)
    {
        if (dictionary.ContainsKey(item.Id)) 
        {
            Debug.LogError($&quot;키 중복: {item.Id}&quot;);
        }
        else
        {
            dictionary.Add(item.Id, item.String);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Get(string Key) : 키에 해당하는 벨류 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public string Get(string key)
{
    if (!dictionary.ContainsKey(key))
    {
        return Unknown;
    }

    return dictionary[key];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DataTableManager&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;옵션 : 디자인패턴의 일부 넣어서 사용 또는 싱글톤 또는 static 멤버로 만들어 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 간단 : static 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;tables : 게임에서 사용할 테이블들을 담아놓는 필드다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 테이블 ID로 미리 저장한 데이터 테이블의 데이터를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private static readonly Dictionary&amp;lt;string, DataTable&amp;gt; tables = new Dictionary&amp;lt;string, DataTable&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Get&amp;lt;T&amp;gt;(string id) : 언제든 원할때 키를 호출해서 값을 사용하는 것이다.&lt;/li&gt;
&lt;li&gt;Init() : 미리 테이블들을 가져오는 함수다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;    //모든 데이터 테이블 가져오기
    private static void Init()
    {
#if UNITY_EDITOR
        foreach(var id in DataTableIds.StringTableIds)
        {
            var table = new StringTable();

            //table.Load(&quot;StringTable&quot;);
            table.Load(id);
            tables.Add(id, table);
        }
#else
        var stringTable = new StringTable();

        //table.Load(&quot;StringTable&quot;);
        stringTable.Load(DataTableIds.String); //현재 언어설정의 언어 테이블 로드
        tables.Add(DataTableIds.String, stringTable);
#endif

    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataTableIds : 모든 데이터 테이블의 id 저장&lt;/li&gt;
&lt;li&gt;public static class DataTableIds { public static readonly string[] StringTableIds = { &quot;StringTableKr&quot;, &quot;StringTableEn&quot;, &quot;StringTableJp&quot;, }; public static string String =&amp;gt; StringTableIds[(int)Variables.Language]; //언어 선택 } public static class Variables { public static Languages Language = Languages.Korean; }&lt;/li&gt;
&lt;li&gt;에디터에서 변경된것을 미리 보기위해 분기를 나눴다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에디터 : 내가 들고있는 모든 데이터 테이블의 아이디를 전부 가져오기&lt;/li&gt;
&lt;li&gt;플레이 중 : 현재 설정된 아이디로만 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에디터 커스터마이징&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[CustomEditor(typeof(LocalizationTest))] : 해당 클래스의 인스펙터 노출을 오버라이드한다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;[CustomEditor(typeof(LocalizationTest))] //해당 클래스의 인스펙터 노출을 오버라이드한다는 의미이다.
public class LocalizationTextEditor : Editor
{
    //인스펙터 GUI 갱신 함수다. / 마우스 클릭처럼 특정 포커스나 입력 이벤트가 있을때만 호출되는 함수다.
    public override void OnInspectorGUI()
    {
        //target : 해당 클래스의 컴포넌트를 의미한다.
        var text = target as LocalizationTest;
        var newId = EditorGUILayout.TextField(&quot;String ID&quot;, text.stringId); //스트링을 그려주고 읽는 함수다. / (라벨, 내용)
        text.stringId = newId;
        var newLang = (Languages)EditorGUILayout.EnumPopup(&quot;Language&quot;, text.editorLang); //열거형을 그려주고 읽는 함수다. / 열거형은 System.enum 형으로 반환되기 때문에 형변환이 필수다.

        //아이디나 언어가 바뀌었다면
        if(newId != text.stringId || newLang != text.editorLang)
        {
            text.stringId = newId;
            text.editorLang = newLang;

            text.OnChangeLanguage(text.editorLang);

            EditorUtility.SetDirty(text); //갱신할 객체를 설정하는 함수다. / 사용 이유 : 여기만 갱신되고 다른곳은 갱신이 안되기 때문이다. / 에디터상에서 제대로 갱신되도록 하려하기 위해서다.
            //더티 플래그를 할당받은 객체만 갱신이 된다. -&amp;gt; 즉, 이 함수는 더티 플래그를 할당하는 것이다.
            //이걸 안하면 에디터에서 바꿨는데도 바뀌지 않는 오류가 있다.
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OnInspectorGUI() : 인스펙터 GUI 갱신 함수다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마우스 클릭처럼 특정 포커스나 입력 이벤트가 있을때만 호출되는 함수다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;target : 해당 클래스의 컴포넌트를 의미한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/Unity</category>
      <category>CSVHelper</category>
      <category>NuGetForUnity</category>
      <category>Resources</category>
      <category>에디터 커스터마이징</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/243</guid>
      <comments>https://thddlwnsrb.tistory.com/243#entry243comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:55:55 +0900</pubDate>
    </item>
    <item>
      <title>Json, 외부 라이브러리 사용법</title>
      <link>https://thddlwnsrb.tistory.com/242</link>
      <description>&lt;h1&gt;Json&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Json 유틸리티 또는 외부 라이브러리로 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;FromJson(Json 문자열)&lt;/li&gt;
&lt;li&gt;ToJson(경로)&lt;/li&gt;
&lt;li&gt;Json 유틸리티 단점 ; Dictionary를 지원하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key, Value 형식으로 데이터를 저장한다.&lt;/li&gt;
&lt;li&gt;Key는 &amp;lsquo;,&amp;rsquo; 콤마로 구분한다.&lt;/li&gt;
&lt;li&gt;모든 Key와 Value는 {} 중괄호 안에 정의된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;{
	&quot;object&quot; : {
	&quot;a&quot;; &quot;b&quot;,
	&quot;c&quot;: &quot;d&quot;
	},
	~~~
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 object도 하나의 Key Value 페어가 될 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 독립적인 Json으로 취급한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;object, array, bool, string, int 등이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[
  {
    &quot;position&quot;: {
      &quot;X&quot;: 5.17811775,
      &quot;Y&quot;: 4.5870347,
      &quot;Z&quot;: -1.49516964
    },
    &quot;rotation&quot;: {
      &quot;X&quot;: 0.333858728,
      &quot;Y&quot;: 0.418717951,
      &quot;Z&quot;: 0.0449003465
    },
    &quot;scale&quot;: {
      &quot;X&quot;: 1.92924368,
      &quot;Y&quot;: 1.26624584,
      &quot;Z&quot;: 1.38748074
    },
    &quot;color&quot;: {
      &quot;R&quot;: 0.757071,
      &quot;G&quot;: 0.979342759,
      &quot;B&quot;: 0.207612664,
      &quot;A&quot;: 1.0
    }
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작성방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;직접 타이핑
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;괄호 구분과 &amp;lsquo;,&amp;rsquo; 로 구분한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;JsonUtility
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;객체 직렬화 형식으로 사용할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부 라이브러리 사용법&lt;/h2&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;com.unity.nuget.newtonsoft-json
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pakage Manager &amp;rarr; 좌측 상단 + 버튼 클릭 &amp;rarr; Install pakage by name 클릭 &amp;rarr; 깃 이름 경로 입력 &amp;rarr; 인스톨&lt;/li&gt;
&lt;li&gt;이후 using Newtonsoft.Json; 스크립트에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;using Newtonsoft.Json;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 라이브러리 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vector3는 넣지 못한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vector3 obj;&lt;/li&gt;
&lt;li&gt;obj.position.normalized처럼 객체를 통해서 재귀적으로 나의 데이터형을 참조하는 방법을 못쓰기 때문이다.&lt;/li&gt;
&lt;li&gt;따로 이게 가능하도록 코드를 작성한 다음에 그걸 가져와서 써야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JsonConveter를 재정의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class Vector3Converter : JsonConverter&amp;lt;Vector3&amp;gt;
{
    public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        Vector3 v = Vector3.zero;
        JObject jobj = JObject.Load(reader); //JObject 객체 생성

        //&quot;&quot;는 내가 작성하는 이름
        v.x = (float)jobj[&quot;X&quot;]; //Jobject 형식으로 넘어오기에 알맞은 데이터형으로 형변환 해줘야한다.
        v.y = (float)jobj[&quot;Y&quot;]; 
        v.z = (float)jobj[&quot;Z&quot;]; 

        return v;
    }

    public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
    {
        writer.WriteStartObject(); //중괄효 열기

        //키 이름을 지정하고, 벨류를 할당해야한다.
        writer.WritePropertyName(&quot;X&quot;);
        writer.WriteValue(value.x);
        writer.WritePropertyName(&quot;Y&quot;);
        writer.WriteValue(value.y);
        writer.WritePropertyName(&quot;Z&quot;);
        writer.WriteValue(value.z);

        writer.WriteEndObject(); //중괄호 닫기
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경로 설정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Application.persistentDataPath : AppData의 프로젝트 폴더에 저장되는 경로를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;var path = Path.Combine(Application.persistentDataPath, &quot;test.json&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통은 텍스트 파일을 통째로 읽고 쓰는 작업을 추가로 해준다.&lt;/li&gt;
&lt;li&gt;경로를 지정할때는 기본 경로 말고 특정 경로를 사용해야한다. =&amp;gt; Application.persistentDataPath
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이유 : 어떤 플랫폼이든 정상적으로 동작할 수 있도록 하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/Unity</category>
      <category>JSON</category>
      <category>외부 라이브러리 사용법</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/242</guid>
      <comments>https://thddlwnsrb.tistory.com/242#entry242comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:51:46 +0900</pubDate>
    </item>
    <item>
      <title>파일 입출력, 유니티 파일 입출력, Using Streams, MemoryStream, Path</title>
      <link>https://thddlwnsrb.tistory.com/241</link>
      <description>&lt;h1&gt;Stream Architecture&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일은 보통 &amp;lsquo;&lt;b&gt;단방향&lt;/b&gt;&amp;rsquo;으로 읽고 쓰고를 진행한다.&lt;/li&gt;
&lt;li&gt;대표적 파일 입출력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콘솔 입출력&lt;/li&gt;
&lt;li&gt;IDE 입출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream adapters, Decorator streams, Backing store streams는 전부 클래스 이름이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream adapters : 텍스트 데이터, 바이너리 데이터, Json 데이터 등을 &lt;b&gt;읽고 쓰는 작업&lt;/b&gt;을 담당한다.&lt;/li&gt;
&lt;li&gt;Decorator streams : 데이터를 감싸는 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GZipStream : 압축 기능이 모여져있는 클래스다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;압축을 다루는 스트림에 입력과 출력을 하는것과 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Backing store streams : 특정 하드웨어와 매칭하고, 데이터를 주고받는 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주로 &lt;b&gt;FileStream&lt;/b&gt;과 &lt;b&gt;MemoryStream&lt;/b&gt;을 사용한다.&lt;/li&gt;
&lt;li&gt;FileStream : 파일 읽고 쓰기 등을 가능하게 하는 기능이 모여져있는 클래스다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 읽고 쓰기는 데이터 &amp;lsquo;비트&amp;rsquo;들을 주고 받는것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 이외에도 여러 작업을 담당하는 기능들이 있다.&lt;/li&gt;
&lt;li&gt;순차적으로 데이터를 읽고 씀
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stream처럼 &lt;b&gt;단방향&lt;/b&gt;으로 데이터가 흐르는것을 생각하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세부 구현 노출 X, 표준 메소드 세트로 읽기, 쓰기, 위치 조정&lt;/li&gt;
&lt;li&gt;한 바이트 또는 임의의 바이트 블럭 단위로 데이터를 순서대로 읽고 씀&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유니티 파일 입출력&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Json 라이브러리&lt;/li&gt;
&lt;li&gt;CSV 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Using Streams&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 스트림 클래스들의 최상위 클래스다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream s : 추상 클래스&lt;/li&gt;
&lt;li&gt;using 구문 : 예외처리와 관계가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에외가 나거나 말거나 FileStream의 dispose 메서드를 호출한다.&lt;/li&gt;
&lt;li&gt;예외가 나서 중지된다면 파일 열어놓은것을 처리해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dispose 메서드에 그러한 작업을 처리하는 구문이 담겨져있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FileSteam(파일 경로 - 상대 경로, 파일 모드)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대 경로는 항상 기준이 필요하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 어플리케이션의 기준은 실행 파일이 기준이 된다.&lt;/li&gt;
&lt;li&gt;C++때는 실행파일 기본경로와 다른 기본경로가 달랐지만, C#은 기본 경로가 같아서 신경쓰지 않아도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기본동작이 &lt;b&gt;바이너리 데이터&lt;/b&gt;를 읽고 쓰는것으로 되어있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, 기본이 &amp;lsquo;바이트 단위&amp;rsquo;로 읽고 쓰기를 진행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Write(바이트 배열, 시작 인덱스, 배열 요소 갯수) : 경로의 파일에 처음부터 요소 갯수만큼 쓰겠다는 것이다.&lt;/li&gt;
&lt;li&gt;Read(바이트 배열, 카운트, 요소 갯수) : 바이트 배열을 카운트부터 요소 갯수만큼 읽어오는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 실재로 쓰여진 요소를 읽어오는것이다.&lt;/li&gt;
&lt;li&gt;왜 마지막의 출력은 0인 것인가? ; 하나씩 읽어 5를 반환하니 현재 위치가 마지막 요소 위치로 저장되었기에 0의 자리는 마지막 요소의 위치가 되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WriteByte : 파일에 바이트형 정수를 작성하는 함수&lt;/li&gt;
&lt;li&gt;FileMode가 Create이니 실재로 파일이 생성된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장자는 txt이지만 FileStream은 &amp;lsquo;바이너리 데이터&amp;rsquo;로 읽고 쓰기가 진행되기에 저장되는 데이터는 바이너리 파일로 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;byte[] data = new byte[1000] : 1000개의 버퍼를 생성하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MemoryStream&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;FileStream sourceStream;

var ms = new MemoryStream();
sourceStream.CopyTo (ms);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크가 제일 느리다 &amp;lt; 보조기억장치 &amp;lt; cpu 단의 메모리&lt;/li&gt;
&lt;li&gt;FileStream은 보조기억장치와 속도가 맞춰진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 느리다는 것이다.&lt;/li&gt;
&lt;li&gt;지금은 네트워크도 빨라져서 속도 차이가 많이 안난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파일에서 스트리밍하는게 아니라 파일을 올리고 메모리에서 스트리밍하는게 더 속도가 빠르다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세부적인 조작을 메모리 스트림에서 하는것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Path&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일경로나 디렉토리 경로를 만드는데 도움이 되는 기능이 모여진 클래스다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;string dir  = @&quot;c:\\mydir&quot;;    // or /mydir
string file = &quot;myfile.txt&quot;;
string path = @&quot;c:\\mydir\\myfile.txt&quot;;    // or /mydir/myfile.txt

Directory.SetCurrentDirectory (@&quot;k:\\demo&quot;);    // or /demo
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SetCurrentDirectory : 현재 경로 설정 함수다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Directory.GetCurrentDirectory()	// k:\\demo\\ or /demo
Path.IsPathRooted (file)	// False
Path.IsPathRooted (path)	// True
Path.GetPathRoot (path)	// c:\\ or /
Path.GetDirectoryName (path)	// c:\\mydir or /mydir
Path.GetFileName (path)	// myfile.txt
Path.GetFullPath (file)	// k:\\demo\\myfile.txt or /demo/myfile.txt
Path.Combine (dir, file)	// c:\\mydir\\myfile.txt or /mydir/myfile.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GetCurrentDirectory : 현재 경로 반환&lt;/li&gt;
&lt;li&gt;IsPathRooted : 최상위 경로인지 확인&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/C#</category>
      <category>MemoryStream</category>
      <category>Path</category>
      <category>Using Streams</category>
      <category>유니티 파일 입출력</category>
      <category>파일 입출력</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/241</guid>
      <comments>https://thddlwnsrb.tistory.com/241#entry241comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:51:12 +0900</pubDate>
    </item>
    <item>
      <title>LINQ 연산자</title>
      <link>https://thddlwnsrb.tistory.com/240</link>
      <description>&lt;h1&gt;C# LINQ 연산자 소개&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LINQ 연산자 개요&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연산자 분류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LINQ 연산자는 세 가지 범주로 분류됨.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시퀀스 입력, 시퀀스 출력 (sequence&amp;rarr;sequence)&lt;/li&gt;
&lt;li&gt;시퀀스 입력, 단일 요소 또는 스칼라 값 출력&lt;/li&gt;
&lt;li&gt;입력 없음, 시퀀스 출력 (생성 메서드)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시퀀스&amp;rarr;시퀀스 연산자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 구조&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관계형 (Relational):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블과 테이블 간 관계로 표현.&lt;/li&gt;
&lt;li&gt;SQL 데이터베이스에서 흔히 사용.&lt;/li&gt;
&lt;li&gt;데이터 분산 저장 및 조인 연산으로 연결.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층적 (Hierarchical):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트리 구조로 표현.&lt;/li&gt;
&lt;li&gt;부모-자식 관계.&lt;/li&gt;
&lt;li&gt;XML, JSON 등에서 자주 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫 (Flat):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 데이터가 하나의 컬렉션에 포함.&lt;/li&gt;
&lt;li&gt;중첩 구조 없음.&lt;/li&gt;
&lt;li&gt;배열, 리스트 형태로 표현.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변환 방법&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관계형 &amp;rarr; 플랫:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SelectMany: 여러 컬렉션을 하나로 평탄화. (예: 고객 목록 + 각 고객의 주문 목록 &amp;rarr; 모든 주문 목록)&lt;/li&gt;
&lt;li&gt;Join: 공통 속성을 기준으로 두 개 이상 컬렉션 연결. (예: 고객 테이블 ⋈ 주문 테이블 &amp;rarr; 고객 정보 + 주문 정보)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관계형 &amp;rarr; 계층적:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브쿼리 (Select-subquery): 다른 쿼리 내부에 포함된 쿼리 사용. (예: 각 고객에 대한 주문 목록을 서브쿼리로 가져옴)&lt;/li&gt;
&lt;li&gt;GroupJoin: 두 컬렉션 연결 후 오른쪽 컬렉션 요소들을 왼쪽 컬렉션 요소에 그룹화. (예: 고객 테이블 ▷◁ 주문 테이블 &amp;rarr; 각 고객에 대한 주문 목록 그룹)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층적 &amp;rarr; 플랫:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SelectMany: 계층 구조를 평탄화.&lt;/li&gt;
&lt;li&gt;Group: 특정 속성 기준으로 요소 그룹화 후 각 그룹 요소들을 하나의 컬렉션으로 가져옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫 &amp;rarr; 계층적:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브쿼리 또는 Group 사용. (예: 상품 목록을 카테고리별로 그룹화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주요 LINQ 연산자&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SelectMany: 컬렉션 평탄화.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러개의 시퀀스를 받아 하나의 시퀀스로 반환하는 연산자다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Join: 컬렉션 연결.&lt;/li&gt;
&lt;li&gt;서브쿼리: 쿼리 내 쿼리.&lt;/li&gt;
&lt;li&gt;GroupJoin: 그룹화된 조인.&lt;/li&gt;
&lt;li&gt;Group: 요소 그룹화.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기능 분류 메서드 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;필터링 (Filtering)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Where&lt;/td&gt;
&lt;td&gt;주어진 조건에 맞는 요소만 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Take&lt;/td&gt;
&lt;td&gt;시퀀스의 시작부터 지정된 개수의 요소만 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;TakeLast&lt;/td&gt;
&lt;td&gt;시퀀스의 끝에서부터 지정된 개수의 요소만 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;TakeWhile&lt;/td&gt;
&lt;td&gt;주어진 조건을 만족하는 동안의 요소만 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Skip&lt;/td&gt;
&lt;td&gt;시퀀스의 시작부터 지정된 개수의 요소를 건너뜀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SkipLast&lt;/td&gt;
&lt;td&gt;시퀀스의 끝에서부터 지정된 개수의 요소를 건너뜀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SkipWhile&lt;/td&gt;
&lt;td&gt;주어진 조건을 만족하는 동안의 요소를 건너뜀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Distinct&lt;/td&gt;
&lt;td&gt;중복된 요소를 제거함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;DistinctBy&lt;/td&gt;
&lt;td&gt;지정된 키 선택기를 사용하여 중복된 요소를 제거함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;투영 (Projecting)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;각 요소를 람다 함수를 사용하여 새로운 형태로 변환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SelectMany&lt;/td&gt;
&lt;td&gt;중첩된 시퀀스를 하나의 평면화된 시퀀스로 변환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;조인 (Joining)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Join&lt;/td&gt;
&lt;td&gt;두 시퀀스의 요소를 지정된 조건에 따라 내부 조인함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;GroupJoin&lt;/td&gt;
&lt;td&gt;두 시퀀스의 요소를 지정된 조건에 따라 그룹 조인함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Zip&lt;/td&gt;
&lt;td&gt;두 시퀀스의 동일한 인덱스에 있는 요소를 쌍으로 묶음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;정렬 (Ordering)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;OrderBy&lt;/td&gt;
&lt;td&gt;오름차순으로 요소를 정렬함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;OrderByDescending&lt;/td&gt;
&lt;td&gt;내림차순으로 요소를 정렬함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;ThenBy&lt;/td&gt;
&lt;td&gt;OrderBy 또는 OrderByDescending 이후 추가 정렬 조건을 지정함 (오름차순)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;ThenByDescending&lt;/td&gt;
&lt;td&gt;OrderBy 또는 OrderByDescending 이후 추가 정렬 조건을 지정함 (내림차순)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reverse&lt;/td&gt;
&lt;td&gt;시퀀스의 요소 순서를 반전함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;그룹화 (Grouping)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;GroupBy&lt;/td&gt;
&lt;td&gt;지정된 키 선택기를 기준으로 요소를 그룹화함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Chunk&lt;/td&gt;
&lt;td&gt;시퀀스를 지정된 크기의 청크(하위 시퀀스)로 분할함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;집합 연산자 (Set operators)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Concat&lt;/td&gt;
&lt;td&gt;두 시퀀스를 연결함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Union&lt;/td&gt;
&lt;td&gt;두 시퀀스의 합집합을 반환함 (중복 제거)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;UnionBy&lt;/td&gt;
&lt;td&gt;지정된 키 선택기를 사용하여 두 시퀀스의 합집합을 반환함 (중복 제거)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Intersect&lt;/td&gt;
&lt;td&gt;두 시퀀스의 교집합을 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;IntersectBy&lt;/td&gt;
&lt;td&gt;지정된 키 선택기를 사용하여 두 시퀀스의 교집합을 반환함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Except&lt;/td&gt;
&lt;td&gt;첫 번째 시퀀스에는 있지만 두 번째 시퀀스에는 없는 요소를 반환함 (차집합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;ExceptBy&lt;/td&gt;
&lt;td&gt;지정된 키 선택기를 사용하여 첫 번째 시퀀스에는 있지만 두 번째 시퀀스에는 없는 요소를 반환함 (차집합)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시퀀스&amp;rarr;요소 또는 값 연산자&lt;/h3&gt;
&lt;p&gt;기능 분류 메서드 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;요소 연산자 (Element operators)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;First&lt;/td&gt;
&lt;td&gt;시퀀스의 첫 번째 요소를 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;FirstOrDefault&lt;/td&gt;
&lt;td&gt;시퀀스의 첫 번째 요소를 반환하거나, 시퀀스가 비어 있는 경우 기본값을 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Last&lt;/td&gt;
&lt;td&gt;시퀀스의 마지막 요소를 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;LastOrDefault&lt;/td&gt;
&lt;td&gt;시퀀스의 마지막 요소를 반환하거나, 시퀀스가 비어 있는 경우 기본값을 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Single&lt;/td&gt;
&lt;td&gt;시퀀스의 유일한 요소를 반환함. 시퀀스에 요소가 없거나 두 개 이상인 경우 예외를 발생시킴.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SingleOrDefault&lt;/td&gt;
&lt;td&gt;시퀀스의 유일한 요소를 반환하거나, 시퀀스가 비어 있는 경우 기본값을 반환함. 시퀀스에 두 개 이상의 요소가 있는 경우 예외를 발생시킴.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;ElementAt&lt;/td&gt;
&lt;td&gt;지정된 인덱스에 있는 요소를 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;ElementAtOrDefault&lt;/td&gt;
&lt;td&gt;지정된 인덱스에 있는 요소를 반환하거나, 인덱스가 범위를 벗어난 경우 기본값을 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;집계 메서드 (Aggregation methods)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Aggregate&lt;/td&gt;
&lt;td&gt;시퀀스의 모든 요소에 누적 함수를 적용함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Average&lt;/td&gt;
&lt;td&gt;시퀀스 요소의 평균값을 계산함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Count&lt;/td&gt;
&lt;td&gt;시퀀스에 있는 요소의 개수를 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;LongCount&lt;/td&gt;
&lt;td&gt;시퀀스에 있는 요소의 개수를 long 형식으로 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Sum&lt;/td&gt;
&lt;td&gt;시퀀스 요소의 합계를 계산함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;시퀀스 요소의 최대값을 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Min&lt;/td&gt;
&lt;td&gt;시퀀스 요소의 최소값을 반환함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;수량자 (Quantifiers)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;시퀀스의 모든 요소가 지정된 조건을 만족하는지 확인함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;시퀀스에 하나 이상의 요소가 지정된 조건을 만족하는지 확인함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Contains&lt;/td&gt;
&lt;td&gt;시퀀스에 지정된 요소가 포함되어 있는지 확인함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SequenceEqual&lt;/td&gt;
&lt;td&gt;두 시퀀스가 동일한지 확인함.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;void&amp;rarr;시퀀스 연산자&lt;/h3&gt;
&lt;p&gt;기능 분류 메서드 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;생성 메서드 (Generation methods)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Empty&lt;/td&gt;
&lt;td&gt;요소가 없는 빈 시퀀스를 생성함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Range&lt;/td&gt;
&lt;td&gt;지정된 범위 내의 정수 시퀀스를 생성함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Repeat&lt;/td&gt;
&lt;td&gt;지정된 값을 지정된 횟수만큼 반복하는 시퀀스를 생성함.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 프로그래밍할때는 실시간으로 동작하는 곳에는 사용하지 말아야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요령껏 써라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/C#</category>
      <category>LINQ 연산자</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/240</guid>
      <comments>https://thddlwnsrb.tistory.com/240#entry240comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:43:13 +0900</pubDate>
    </item>
    <item>
      <title>LINQ 하위 쿼리, let 키워드</title>
      <link>https://thddlwnsrb.tistory.com/239</link>
      <description>&lt;h1&gt;LINQ 하위 쿼리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하위 쿼리의 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위 쿼리(Subquery)는 다른 쿼리의 람다 식 내부에 포함된 쿼리를 의미함&lt;/li&gt;
&lt;li&gt;일반적인 C# 식과 마찬가지로 람다 식의 오른쪽에 유효한 식이면 하위 쿼리로 사용할 수 있음&lt;/li&gt;
&lt;li&gt;하위 쿼리는 외부 람다 식의 식 범위에 비공개로 지정되며, 외부 람다 식의 매개변수를 참조할 수 있음&lt;/li&gt;
&lt;li&gt;시간과 메모리에 엄청난 손해가 일어난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하위 쿼리 예제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 하위 쿼리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;음악가들을 성(last name)으로 정렬하는 예제임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Last가 전체를 순회해서 마지막 요소를 반환하는 함수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] musos = { &quot;David Gilmour&quot;, &quot;Roger Waters&quot;, &quot;Rick Wright&quot;, &quot;Nick Mason&quot; };

IEnumerable&amp;lt;string&amp;gt; query = musos.OrderBy(m =&amp;gt; m.Split().Last());

// 결과 출력
foreach (string name in query)
    Console.WriteLine(name);

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;David Gilmour
Nick Mason
Roger Waters
Rick Wright

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;m.Split()은 각 문자열을 단어 컬렉션으로 변환함&lt;/li&gt;
&lt;li&gt;Last()는 하위 쿼리로, 마지막 단어(성)를 선택함&lt;/li&gt;
&lt;li&gt;n^2 의 시간복잡도 연산을 하게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복잡한 하위 쿼리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열에서 가장 짧은 문자열의 길이와 같은 길이를 가진 모든 문자열을 찾는 예제임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비교할때마다 전체를 순회하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

IEnumerable&amp;lt;string&amp;gt; outerQuery = names
    .Where(n =&amp;gt; n.Length == names.OrderBy(n2 =&amp;gt; n2.Length)
                                .Select(n2 =&amp;gt; n2.Length)
                                .First());

// 결과 출력
foreach (string name in outerQuery)
    Console.WriteLine(name);

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Tom
Jay

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 식으로 동일한 쿼리를 작성하면 다음과 같음:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757410925355&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;string&amp;gt; outerQuery =
    from n in names
    where n.Length ==
        (from n2 in names orderby n2.Length select n2.Length).First()
    select n;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 범위 변수(n)가 하위 쿼리의 범위에 있으므로, 하위 쿼리의 범위 변수로 n을 재사용할 수 없음&lt;/li&gt;
&lt;li&gt;이것이 위 예제에서 하위 쿼리의 범위 변수를 n2로 지정한 이유임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Min 연산자를 사용한 간단화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 복잡한 쿼리는 Min 집계 함수를 사용하여 다음과 같이 단순화할 수 있음:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;string&amp;gt; query =
    from n in names
    where n.Length == names.Min(n2 =&amp;gt; n2.Length)
    select n;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하위 쿼리의 실행&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위 쿼리는 외부 람다 식이 평가될 때마다 실행됨&lt;/li&gt;
&lt;li&gt;실행은 외부에서 내부로 진행됨(outside-in)&lt;/li&gt;
&lt;li&gt;로컬 쿼리(LINQ to Objects)에서는 실제로 각 반복마다 하위 쿼리가 실행됨:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 쿼리가 데이터를 순회할 때마다 하위 쿼리가 새로 실행됨&lt;/li&gt;
&lt;li&gt;예를 들어 Where절 안의 하위 쿼리는 각 요소를 필터링할 때마다 다시 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해석된 쿼리(예: 데이터베이스 쿼리)는 이와 달리 서버에서 최적화된 단일 쿼리로 변환되어 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 쿼리의 하위 쿼리 실행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 쿼리에서 하위 쿼리는 외부 루프의 각 반복마다 실행됨&lt;/li&gt;
&lt;li&gt;이는 성능에 영향을 미칠 수 있음&lt;/li&gt;
&lt;li&gt;데이터베이스 쿼리와 달리 단일 유닛으로 처리되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하위 쿼리 최적화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 컬렉션을 쿼리할 때는 하위 쿼리를 별도로 실행하는 것이 일반적으로 더 효율적임:&lt;/li&gt;
&lt;li&gt;비교값이 하나로 고정된다면 먼저 구해놓고 비교하는게 더 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;int shortest = names.Min(n =&amp;gt; n.Length);
IEnumerable&amp;lt;string&amp;gt; query = from n in names
                           where n.Length == shortest
                           select n;

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하면 하위 쿼리가 한 번만 실행됨&lt;/li&gt;
&lt;li&gt;단, 상관 하위 쿼리(correlated subquery, 외부 범위 변수를 참조하는 하위 쿼리)의 경우는 예외임&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };
var query = from name in names
            where name.Length &amp;gt;
                  (from n in names
                   where n != name  // 여기서 외부 범위 변수 name을 참조
                   select n.Length).Average()
            select name;
// name보다 짧은 이름들의 평균 길이보다 긴 이름들을 선택

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 예제에서 하위 쿼리는 외부의 name 변수를 참조하므로 분리할 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 경우는 어쩔 수 없으니 이대로 써야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각 name에 대해 하위 쿼리가 다른 결과를 반환함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하위 쿼리와 지연 실행&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위 쿼리 내에서 First, Count와 같은 즉시 실행 연산자를 사용해도 외부 쿼리는 지연 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

// 하위 쿼리에서 Count를 사용해도 전체 쿼리는 지연 실행됨
var query = names.Where(n =&amp;gt; n.Length &amp;gt; names.Count()/2);

// 이 시점에서 실제로 쿼리가 실행됨
foreach (var name in query)
    Console.WriteLine(name);

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Select에서의 하위 쿼리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Select절에서 하위 쿼리를 사용하면 각 요소마다 새로운 쿼리가 생성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };
var query = names.Select(n =&amp;gt;
    names.Where(other =&amp;gt; other.Length == n.Length));

// 실행 결과는 각 이름별로 같은 길이의 이름들을 포함하는 시퀀스가 됨
foreach (var nameGroup in query)
    foreach (var name in nameGroup)
        Console.WriteLine(name);

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;LINQ 프로젝션 전략&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 초기화자(Object Initializers)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지는 select 절에서 스칼라 요소 형식만을 프로젝션함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반환되는 시퀀스의 일반화 시퀀스가 정해지고, 반환되는 일반화가 정해지는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;C#의 객체 초기화자를 사용하면 더 복잡한 형식으로 프로젝션할 수 있음&lt;/li&gt;
&lt;li&gt;복잡한 데이터를 구조화하여 저장하고 처리할 때 유용함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;활용 시나리오&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다단계 쿼리에서 중간 결과를 저장할 때 활용&lt;/li&gt;
&lt;li&gt;여러 관련 데이터를 하나의 객체로 그룹화할 때 사용&lt;/li&gt;
&lt;li&gt;후속 쿼리에서 필요한 데이터를 구조화할 때 유용함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제: 모음을 제거한 문자열과 원본 문자열 함께 저장하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 임시 프로젝션을 위한 클래스 정의:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class TempProjectionItem
{
    public string Original;    // 원본 이름
    public string Vowelless;   // 모음이 제거된 이름
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 초기화자를 사용하여 프로젝션:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };
IEnumerable&amp;lt;TempProjectionItem&amp;gt; temp =
    from n in names
    select new TempProjectionItem
    {
        Original = n,
        Vowelless = n.Replace(&quot;a&quot;, &quot;&quot;).Replace(&quot;e&quot;, &quot;&quot;).Replace(&quot;i&quot;, &quot;&quot;)
            .Replace(&quot;o&quot;, &quot;&quot;).Replace(&quot;u&quot;, &quot;&quot;)
    };

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝션 결과를 활용한 쿼리:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;string&amp;gt; query = from item in temp
                           where item.Vowelless.Length &amp;gt; 2
                           select item.Original;

// 결과:
// Dick
// Harry
// Mary

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;익명 형식(Anonymous Types)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;익명 형식을 사용하면 특별한 클래스를 작성하지 않고도 중간 결과를 구조화할 수 있음&lt;/li&gt;
&lt;li&gt;컴파일러가 자동으로 임시 클래스를 생성함&lt;/li&gt;
&lt;li&gt;일회성 데이터 구조가 필요할 때 유용함&lt;/li&gt;
&lt;li&gt;코드 작성 시간을 절약하고 가독성을 향상시킬 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앞선 예제를 익명 형식으로 변경&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var intermediate = from n in names
                  select new
                  {
                      Original = n,
                      Vowelless = n.Replace(&quot;a&quot;, &quot;&quot;).Replace(&quot;e&quot;, &quot;&quot;).Replace(&quot;i&quot;, &quot;&quot;)
                          .Replace(&quot;o&quot;, &quot;&quot;).Replace(&quot;u&quot;, &quot;&quot;)
                  };

IEnumerable&amp;lt;string&amp;gt; query = from item in intermediate
                           where item.Vowelless.Length &amp;gt; 2
                           select item.Original;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중요 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간 쿼리의 형식은 IEnumerable&amp;lt;컴파일러-생성-임의-이름&amp;gt; 임&lt;/li&gt;
&lt;li&gt;이러한 형식의 변수는 var 키워드로만 선언 가능함&lt;/li&gt;
&lt;li&gt;var는 단순한 코드 줄임이 아닌 필수 요소임&lt;/li&gt;
&lt;li&gt;IDE의 인텔리센스를 통해 생성된 익명 형식의 속성에 접근 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;into 키워드를 사용한 간결한 표현&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var query = from n in names
            select new
            {
                Original = n,
                Vowelless = n.Replace(&quot;a&quot;, &quot;&quot;).Replace(&quot;e&quot;, &quot;&quot;).Replace(&quot;i&quot;, &quot;&quot;)
                    .Replace(&quot;o&quot;, &quot;&quot;).Replace(&quot;u&quot;, &quot;&quot;)
            }
            into temp
            where temp.Vowelless.Length &amp;gt; 2
            select temp.Original;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;into 키워드의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 연속(Query Continuation)을 위해 사용됨&lt;/li&gt;
&lt;li&gt;select나 group 절 다음에만 사용 가능함&lt;/li&gt;
&lt;li&gt;이전 범위의 변수들은 into 키워드 이후에 접근할 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서는 n에 접근할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, reset 하는 느낌이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;let 키워드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 표현식에서 새로운 변수를 도입하는 키워드임&lt;/li&gt;
&lt;li&gt;범위 변수와 함께 새로운 변수를 사용할 수 있게 함&lt;/li&gt;
&lt;li&gt;복잡한 계산 결과를 재사용할 때 유용함&lt;/li&gt;
&lt;li&gt;즉, 지역변수를 선언하는 느낌이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제: let을 사용한 모음 제거 쿼리&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };
IEnumerable&amp;lt;string&amp;gt; query =
    from n in names
    let vowelless = n.Replace(&quot;a&quot;, &quot;&quot;).Replace(&quot;e&quot;, &quot;&quot;).Replace(&quot;i&quot;, &quot;&quot;)
        .Replace(&quot;o&quot;, &quot;&quot;).Replace(&quot;u&quot;, &quot;&quot;)
    where vowelless.Length &amp;gt; 2
    orderby vowelless
    select n;  // let 덕분에 n이 여전히 범위 내에 있음

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;let의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 요소를 기존 요소와 함께 프로젝션함&lt;/li&gt;
&lt;li&gt;쿼리 내에서 표현식을 반복 작성하지 않고도 여러 번 사용할 수 있음&lt;/li&gt;
&lt;li&gt;select 절에서 원본 이름(n)이나 모음이 제거된 버전(vowelless) 모두 선택 가능함&lt;/li&gt;
&lt;li&gt;코드의 가독성과 유지보수성이 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;let 사용 규칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;where 문 앞이나 뒤에 여러 개의 let 문을 작성할 수 있음&lt;/li&gt;
&lt;li&gt;let 문은 이전 let 문에서 도입된 변수를 참조할 수 있음 (into 절의 경계를 넘지 않는 범위에서)&lt;/li&gt;
&lt;li&gt;let 표현식은 스칼라 형식뿐만 아니라 하위 시퀀스 등도 가능함&lt;/li&gt;
&lt;li&gt;let 표현식의 결과는 쿼리의 나머지 부분에서 재사용 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일러의 let 처리 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일러는 let 절을 임시 익명 형식으로 프로젝션하여 처리함&lt;/li&gt;
&lt;li&gt;범위 변수와 새로운 표현식 변수를 모두 포함하는 임시 익명 형식을 생성함&lt;/li&gt;
&lt;li&gt;이는 이전 예제의 익명 형식을 사용한 방식과 동일한 결과를 만들어냄&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/C#</category>
      <category>let 키워드</category>
      <category>LINQ 하위 쿼리</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/239</guid>
      <comments>https://thddlwnsrb.tistory.com/239#entry239comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:42:30 +0900</pubDate>
    </item>
    <item>
      <title>LINQ 질의식, LINQ 지연 실행</title>
      <link>https://thddlwnsrb.tistory.com/238</link>
      <description>&lt;h1&gt;LINQ 질의식&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Query Expressions 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질의식(Query Expression)은 LINQ 쿼리를 작성하는 C#의 선언적 구문이며, 가독성을 높인 구문임.&lt;/li&gt;
&lt;li&gt;데이터 컬렉션을 다루는 간결한 방식을 제공함.&lt;/li&gt;
&lt;li&gt;질의식은 컴파일러에 의해 Fluent Syntax(메서드 호출 구문)로 변환됨.&lt;/li&gt;
&lt;li&gt;예를 들어, 다음 Query Expression은:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;from n in names
select n

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;다음과 같은 Fluent Syntax로 변환됨.

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;names.Select(n =&amp;gt; n)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 구문&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 질의식은 from 절로 시작하고 select 나 group 절로 끝나야 함.&lt;/li&gt;
&lt;li&gt;from 절은 데이터 소스를 지정하고 범위 변수를 정의함.&lt;/li&gt;
&lt;li&gt;where 절은 필터링 조건을 지정함.&lt;/li&gt;
&lt;li&gt;orderby 절은 정렬 기준을 지정함.&lt;/li&gt;
&lt;li&gt;select 절은 결과로 반환할 데이터를 정의함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;from과 마지막 사이는 서로간의 순서는 중요하지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예제:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757410737498&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; }; // 배열 초기화

IEnumerable&amp;lt;string&amp;gt; query =
	from n in names // names 배열에서 각 요소를 n으로 가져옴
	where n.Contains(&quot;a&quot;) // n에 &quot;a&quot;가 포함된 요소만 필터링
	orderby n.Length // n의 길이를 기준으로 오름차순 정렬
	select n.ToUpper(); // n을 대문자로 변환하여 선택

foreach (string name in query)
	Console.WriteLine(name);

// 출력:
// JAY
// MARY
// HARRY&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴파일러의 Query Expression 처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일러는 Query Expression을 Fluent Syntax로 변환함.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;string&amp;gt; query = names
	.Where(n =&amp;gt; n.Contains(&quot;a&quot;))
	.OrderBy(n =&amp;gt; n.Length)
	.Select(n =&amp;gt; n.ToUpper());

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where, OrderBy, Select는 System.Linq 네임스페이스의 확장 메서드로 해석됨.&lt;/li&gt;
&lt;li&gt;컴파일러는 Query Expression을 Where, OrderBy, Select 등의 메서드 호출로 변환하며, 이 메서드들은 IEnumerable&amp;lt;T&amp;gt; 인터페이스를 구현하는 객체의 확장 메서드로 호출됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;범위 변수(Range Variable)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;from 키워드 다음의 식별자를 범위 변수라고 함.&lt;/li&gt;
&lt;li&gt;범위 변수는 입력 시퀀스의 각 요소를 순회하며 가리키는 변수임.&lt;/li&gt;
&lt;li&gt;범위 변수는 쿼리의 각 절에서 서로 다른 시퀀스를 열거함.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;from n in names    // n은 원본 배열에서 직접 가져옴
where n.Contains(&quot;a&quot;) // n은 필터링되기 전 결과에서 가져옴
orderby n.Length    // n은 정렬 전 결과에서 가져옴
select n.ToUpper()    // n은 정렬된 결과에서 가져옴

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;범위 변수는 각 람다 식에서 개별적으로 범위가 지정됨.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 이름이지만 참조 변수 입장에서는 각자 다른 변수로 취급해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;names.Where(n =&amp;gt; n.Contains(&quot;a&quot;))    // n은 이 람다 식에서만 유효
	.OrderBy(n =&amp;gt; n.Length)        // n은 이 람다 식에서만 유효
	.Select(n =&amp;gt; n.ToUpper())        // n은 이 람다 식에서만 유효

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Query Syntax vs Fluent Syntax&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Query Syntax 장점:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;let 절을 사용해 새 변수를 도입할 때 더 명확함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;let : 쿼리 안에서 사용할 지역변수같은 느낌이다.&lt;/li&gt;
&lt;li&gt;let절이 없다면 같은연산을 한 변수선언 안에 여러번 해줘야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Query Syntax
//let절이 없을경우
var query = from n in names
			where n.ToUpper().Contains(&quot;A&quot;)
			select n.ToUpper();
			
//let절 사용하는 경우
var query = from n in names
			let uppercaseName = n.ToUpper()
			where uppercaseName.Contains(&quot;A&quot;)
			select uppercaseName;

// Fluent Syntax
//주의 : n으로 계속 이름을 바꾸지 않고 사용하는 경우 -&amp;gt; 지역변수가 사라지지않아 오류가 날 수 있다.
var query = names.Select(n =&amp;gt; n.ToUpper())
				 .Where(uppercaseName =&amp;gt; uppercaseName.Contains(&quot;A&quot;));

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SelectMany, Join, GroupJoin 뒤에 외부 범위 변수 참조가 필요할 때 유용함.&lt;/li&gt;
&lt;li&gt;복잡한 쿼리를 더 읽기 쉽게 표현할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fluent Syntax 장점:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 연산자만 사용하는 간단한 쿼리에 더 간결함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: names.Where(n =&amp;gt; n.Contains(&quot;a&quot;))&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Query Syntax에서 지원하지 않는 연산자(Count, First, Aggregate 등)를 직접 사용할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Query Syntax가 지원하는 연산자:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where, Select, SelectMany&lt;/li&gt;
&lt;li&gt;OrderBy, ThenBy(오름차순으로 정렬 후, 추가로 적용할 오름차순 정렬을 정의함), OrderByDescending, ThenByDescending&lt;/li&gt;
&lt;li&gt;GroupBy, Join, GroupJoin&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;혼합 구문 쿼리(Mixed-Syntax Queries)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Query Syntax와 Fluent Syntax를 함께 사용할 수 있음.&lt;/li&gt;
&lt;li&gt;제한사항: 각 쿼리 식은 완전해야 함(from으로 시작하고 select/group으로 끝나야 함).&lt;/li&gt;
&lt;li&gt;시퀀스를 넣어 시퀀스를 받을 수 있고, 시퀀스를 넣어 시퀀스의 시퀀스를 반환으로 받을 수 있다.&lt;/li&gt;
&lt;li&gt;예제:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

// 'a'가 포함된 이름의 개수 계산
int matches = (from n in names where n.Contains(&quot;a&quot;) select n).Count();
// 3

// 알파벳 순으로 첫 번째 이름 가져오기
string first = (from n in names orderby n select n).First();
// Dick

// GroupBy와 Count를 함께 사용하는 예시
var groups = (from n in names
			  group n by n.Length into g
			  select new { Length = g.Key, Count = g.Count() });

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 쿼리를 Fluent Syntax로 표현:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;int matches = names.Where(n =&amp;gt; n.Contains(&quot;a&quot;)).Count(); // 3
string first = names.OrderBy(n =&amp;gt; n).First(); // Dick
var groups = names.GroupBy(n =&amp;gt; n.Length)
				 .Select(g =&amp;gt; new { Length = g.Key, Count = g.Count() });

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 참고사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Query Expression을 사용하려면 System.Linq 네임스페이스를 반드시 import 해야 함.&lt;/li&gt;
&lt;li&gt;Query Expression은 컴파일러에 의해 먼저 Fluent Syntax로 변환된 후 컴파일됨.&lt;/li&gt;
&lt;li&gt;Query Syntax와 Fluent Syntax 중 어느 것이 더 좋다고 단정할 수 없음. 쿼리의 복잡성, 가독성, 개인의 선호도 등을 고려하여 선택하는 것이 중요함.&lt;/li&gt;
&lt;li&gt;두 구문을 혼합해서 사용할 때 각각의 장점을 활용하면 가장 명확하고 효율적인 코드를 작성할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;질의문&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬(정렬 기준).ThenBy(정렬 기준) : 1차 정렬로 정렬된 결과를 2차 정렬 기준에 맞춰 다시 정렬한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질의문으로는 orderby에 ,로 정렬기준을 연결해서 작성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;LINQ 지연 실행&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따로 항목이 있다는것은 지연실행이 안되는 오퍼레이터도 있다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지연 실행의 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 쿼리 연산자는 &lt;b&gt;생성될 때가 아닌 열거될 때&lt;/b&gt; 실행됨&lt;/li&gt;
&lt;li&gt;지연 실행은 쿼리 생성과 실행을 분리하여 유연성을 제공함&lt;/li&gt;
&lt;li&gt;이는 delegate의 동작 방식과 유사함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 예제&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var numbers = new List&amp;lt;int&amp;gt; { 1 };
IEnumerable&amp;lt;int&amp;gt; query = numbers.Select(n =&amp;gt; n * 10); // 쿼리 생성
numbers.Add(2);  // 추가 요소 삽입
foreach (int n in query)
    Console.Write(n + &quot;|&quot;);  // 출력: 10|20|

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 생성 후 리스트에 추가된 숫자 2도 결과에 포함됨&lt;/li&gt;
&lt;li&gt;실제 필터링이나 정렬은 &lt;b&gt;foreach 문이 실행될 때 발생&lt;/b&gt;함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즉시 실행되는 연산자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 연산자들은 예외적으로 즉시 실행됨:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 요소나 스칼라 값을 반환하는 연산자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;First&lt;/li&gt;
&lt;li&gt;Count&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변환 연산자 - 특정 컬렉션으로 반환하는 함수들이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ToArray&lt;/li&gt;
&lt;li&gt;ToList&lt;/li&gt;
&lt;li&gt;ToDictionary&lt;/li&gt;
&lt;li&gt;ToLookup&lt;/li&gt;
&lt;li&gt;ToHashSet&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특징 : 반환형이 IEnumerable이 아닌 연산자들이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int matches = numbers.Where(n =&amp;gt; n &amp;lt;= 2).Count();  // 즉시 실행됨

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 재평가&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지연 실행 쿼리는 재열거시 재평가됨&lt;/li&gt;
&lt;li&gt;이는 때로는 장점이 될 수 있고, 때로는 단점이 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var numbers = new List&amp;lt;int&amp;gt;() { 1, 2 };
IEnumerable&amp;lt;int&amp;gt; query = numbers.Select(n =&amp;gt; n * 10);

foreach (int n in query) Console.Write(n + &quot;|&quot;);  // 출력: 10|20|
numbers.Clear();
foreach (int n in query) Console.Write(n + &quot;|&quot;);  // 출력: (없음)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재평가가 바람직하지 않은 경우:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 시점의 결과를 &quot;고정&quot;하거나 캐시하고 싶을 때&lt;/li&gt;
&lt;li&gt;쿼리가 계산 집약적이거나 원격 데이터베이스를 조회할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결책 - ToArray나 ToList 변환 연산자 사용:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var numbers = new List&amp;lt;int&amp;gt;() { 1, 2 };
List&amp;lt;int&amp;gt; timesTen = numbers
    .Select(n =&amp;gt; n * 10)
    .ToList();  // 즉시 실행되어 List&amp;lt;int&amp;gt;에 저장

numbers.Clear();
Console.WriteLine(timesTen.Count);  // 여전히 2

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캡처된 변수(Captured Variables)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리의 람다식이 외부 변수를 캡처하면, 쿼리는 실행 시점의 변수 값을 사용함&lt;/li&gt;
&lt;li&gt;람다식으로 캡처된 결과가 캡처되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int[] numbers = { 1, 2 };
int factor = 10;
IEnumerable&amp;lt;int&amp;gt; query = numbers.Select(n =&amp;gt; n * factor);
factor = 20;
foreach (int n in query) Console.Write(n + &quot;|&quot;);  // 출력: 20|40|

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for 루프에서의 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;char&amp;gt; query = &quot;Not what you might expect&quot;;
string vowels = &quot;aeiou&quot;;

for (int i = 0; i &amp;lt; vowels.Length; i++) //5번 반복
    query = query.Where(c =&amp;gt; c != vowels[i]); //람다식에 반복 변수를 써서 캡처가 되었다.

//반복이 5에 마지막 i++까지 들어가서 배열의 끝이 넘어가게된다.
foreach (char c in query) Console.Write(c);  // IndexOutOfRangeException 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 예:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; vowels.Length; i++)
{
    char vowel = vowels[i];  // 루프 내부에서 새 변수 선언
    query = query.Where(c =&amp;gt; c != vowel);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 foreach 사용:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;foreach (char vowel in vowels)
    query = query.Where(c =&amp;gt; c != vowel);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지연 실행의 내부 동작 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 연산자는 데코레이터 시퀀스를 반환함&lt;/li&gt;
&lt;li&gt;데코레이터 시퀀스는 자체 저장소 없이 입력 시퀀스를 래핑함&lt;/li&gt;
&lt;li&gt;데이터 요청시 입력 시퀀스에 요청을 전달함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제:&lt;/p&gt;
&lt;pre id=&quot;code_1757410768713&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;int&amp;gt; lessThanTen = new int[] { 5, 12, 3 }.Where(n =&amp;gt; n &amp;lt; 10);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 데코레이터 시퀀스 구현도 C# 이터레이터로 쉽게 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static IEnumerable&amp;lt;TResult&amp;gt; MySelect&amp;lt;TSource,TResult&amp;gt;
(this IEnumerable&amp;lt;TSource&amp;gt; source, Func&amp;lt;TSource,TResult&amp;gt; selector)
{
    foreach (TSource element in source)
        yield return selector(element);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데코레이터 체이닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 연산자를 체이닝하면 데코레이터가 계층화됨:&lt;/p&gt;
&lt;pre id=&quot;code_1757410785865&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;int&amp;gt; query = new int[] { 5, 12, 3 }
    .Where(n =&amp;gt; n &amp;lt; 10)
    .OrderBy(n =&amp;gt; n)
    .Select(n =&amp;gt; n * 10);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 쿼리 연산자는 이전 시퀀스를 래핑하는 새로운 데코레이터를 인스턴스화함&lt;/li&gt;
&lt;li&gt;이 객체 모델은 열거 전에 완전히 구성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 실행 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;foreach는 가장 바깥쪽 연산자(Select)의 데코레이터에서 GetEnumerator를 호출&lt;/li&gt;
&lt;li&gt;결과적으로 데코레이터 시퀀스의 체인과 일치하는 열거자 체인이 생성됨&lt;/li&gt;
&lt;li&gt;LINQ 쿼리는 요구 기반 풀 모델(demand-driven pull model)을 따름&lt;/li&gt;
&lt;li&gt;이는 공급 기반 푸시 모델(supply-driven push model)과 대비됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생산 라인 비유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LINQ 쿼리를 생산 라인에 비유하면 다음과 같음:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리는 컨베이어 벨트들로 구성된 생산 라인과 같음&lt;/li&gt;
&lt;li&gt;지연 실행은 이 생산 라인이 &quot;게으른&quot; 특성을 가짐을 의미함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;벨트들은 요청이 있을 때만 요소들을 이동시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쿼리 구성은 생산 라인 설치에 해당함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 것이 준비되어 있지만 아직 움직이지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쿼리 열거시:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 오른쪽 벨트가 먼저 활성화됨&lt;/li&gt;
&lt;li&gt;이는 다른 벨트들의 연쇄적 활성화를 유발함&lt;/li&gt;
&lt;li&gt;입력 시퀀스의 요소들이 필요할 때만 이동함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 요구 기반 풀 모델(demand-driven pull model)의 특징:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요소들은 실제로 필요할 때만 처리됨&lt;/li&gt;
&lt;li&gt;메모리 효율성이 높음&lt;/li&gt;
&lt;li&gt;SQL 데이터베이스 쿼리로의 확장을 자연스럽게 가능하게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 실행의 예&lt;/h3&gt;
&lt;pre id=&quot;code_1757410800832&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IEnumerable&amp;lt;int&amp;gt; query = new int[] { 5, 12, 3 }
    .Where(n =&amp;gt; n &amp;lt; 10)
    .OrderBy(n =&amp;gt; n)
    .Select(n =&amp;gt; n * 10);

foreach (int n in query) Console.Write(n + &quot;|&quot;);  // 출력: 30|50|&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부/C#</category>
      <category>LINQ 지연 실행</category>
      <category>LINQ 질의식</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/238</guid>
      <comments>https://thddlwnsrb.tistory.com/238#entry238comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:41:37 +0900</pubDate>
    </item>
    <item>
      <title>쿼리 연산자 체이닝, 지연 실행의 장점</title>
      <link>https://thddlwnsrb.tistory.com/237</link>
      <description>&lt;h1&gt;LINQ Fluent Syntax&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fluent Syntax는 LINQ의 가장 기본적이고 유연한 쿼리 작성 방식임&lt;/li&gt;
&lt;li&gt;확장 메서드와 람다 식을 사용하여 쿼리를 작성함&lt;/li&gt;
&lt;li&gt;쿼리 연산자를 체이닝하여 복잡한 쿼리를 구성할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 연산자 체이닝&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체이닝은 여러 쿼리 연산자를 연결하여 복잡한 쿼리를 만드는 방법임&lt;/li&gt;
&lt;li&gt;데이터는 왼쪽에서 오른쪽으로 흐르며 순차적으로 처리됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 변수가 저장되는게 아니라 해당 구성의 &amp;lsquo;재료&amp;rsquo;들을 가지고 있는것이고, 이것을 얻기위해 계속 반복을 돌리는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757410670973&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

IEnumerable&amp;lt;string&amp;gt; query = names
&amp;nbsp; &amp;nbsp; .Where(n =&amp;gt; n.Contains(&quot;a&quot;))&amp;nbsp; &amp;nbsp; &amp;nbsp;// &quot;a&quot;를 포함하는 이름 필터링
&amp;nbsp; &amp;nbsp; .OrderBy(n =&amp;gt; n.Length)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 길이순으로 정렬
&amp;nbsp; &amp;nbsp; .Select(n =&amp;gt; n.ToUpper());&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 대문자로 변환

foreach (string name in query)
&amp;nbsp; &amp;nbsp; Console.WriteLine(name);

// 실행 결과:
// JAY
// MARY
// HARRY&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 연산자의 구현과 체이닝&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where, OrderBy, Select는 System.Linq &lt;b&gt;네임스페이스의 확장 메서드&lt;/b&gt;임&lt;/li&gt;
&lt;li&gt;람다 식의 변수 n은 각각의 람다 식에서 독립적으로 &lt;b&gt;스코프&lt;/b&gt;가 지정됨&lt;/li&gt;
&lt;li&gt;쿼리 연산자는 이터레이터를 사용하여 간단하게 구현할 수 있음:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// Select 연산자의 간단한 구현 예제
//TSource : 입력용 일반화 인자, TResult : 출력용 인반화 인자.
public static IEnumerable&amp;lt;TResult&amp;gt; MySelect&amp;lt;TSource,TResult&amp;gt;
&amp;nbsp; &amp;nbsp; (this IEnumerable&amp;lt;TSource&amp;gt; source, Func&amp;lt;TSource,TResult&amp;gt; selector) //TResult : 반환형
{
&amp;nbsp; &amp;nbsp; foreach (TSource element in source)
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; yield return selector(element);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데코레이터 시퀀스와 실행 원리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데코레이터 시퀀스는 입력 시퀀스를 감싸는 래퍼(wrapper) 객체임&lt;/li&gt;
&lt;li&gt;일반적인 컬렉션과 달리 데코레이터는 자체 데이터 저장소를 가지지 않음&lt;/li&gt;
&lt;li&gt;대신 런타임에 제공되는 입력 시퀀스에 영구적으로 의존함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데코레이터 시퀀스 예제&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 다음과 같은 쿼리가 있다고 가정:
IEnumerable&amp;lt;int&amp;gt; lessThanTen = new int[] { 5, 12, 3 }.Where(n =&amp;gt; n &amp;lt; 10);

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 쿼리의 실행 구조:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Where는 데코레이터 객체만 생성함
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;lessThanTen은 int 배열을 참조한다.&lt;/li&gt;
&lt;li&gt;가비지 컬렉션이 동작한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;데코레이터는 입력 배열과 람다 식(n =&amp;gt; n &amp;lt; 10)을 참조로 저장&lt;/li&gt;
&lt;li&gt;lessThanTen을 열거할 때만 실제로 필터링이 수행됨&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데코레이터 체이닝&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 여러 연산자를 체이닝한 쿼리:
IEnumerable&amp;lt;int&amp;gt; query = new int[] { 5, 12, 3 }
&amp;nbsp; &amp;nbsp; .Where(n =&amp;gt; n &amp;lt; 10)
&amp;nbsp; &amp;nbsp; .OrderBy(n =&amp;gt; n)
&amp;nbsp; &amp;nbsp; .Select(n =&amp;gt; n * 10);

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 연산자는 새로운 데코레이터를 만들어 이전 시퀀스를 감쌈
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데코레이터 : 실행의 결과를 다음 연산이 감싸는 식으로 진행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실행 순서:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Where 데코레이터가 원본 배열을 감쌈&lt;/li&gt;
&lt;li&gt;OrderBy 데코레이터가 Where의 결과를 감쌈&lt;/li&gt;
&lt;li&gt;Select 데코레이터가 OrderBy의 결과를 감쌈&lt;/li&gt;
&lt;li&gt;최종적으로 러시안 인형처럼 중첩된 데코레이터 체인이 형성됨&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지연 실행(Deferred Execution)의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 구성과 실행이 분리됨&lt;/li&gt;
&lt;li&gt;쿼리를 여러 단계로 구성할 수 있음&lt;/li&gt;
&lt;li&gt;데이터베이스 쿼리 등 원격 데이터 소스 지원이 가능해짐&lt;/li&gt;
&lt;li&gt;필요할 때만 실제 데이터를 처리하므로 메모리 효율적임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의해야 할 점&lt;/b&gt;: 쿼리 실행 전에 원본 데이터가 변경되면, 쿼리 결과에 영향을 줄 수 있음. 쿼리 결과를 즉시 확정하려면 ToList() 또는 ToArray() 같은 즉시 실행 메서드 사용이 필요.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터가 변경되면 쿼리 실행 결과도 바뀌게된다.&lt;/li&gt;
&lt;li&gt;안바뀌게 하려면? : List나 Array처럼 즉시 실행 메서드가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 단계별 쿼리 구성 예제:
var numbers = new List&amp;lt;int&amp;gt;() { 1, 2 };
IEnumerable&amp;lt;int&amp;gt; query = numbers.Select(n =&amp;gt; n * 10); // 쿼리 구성

numbers.Add(3);&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 원본 데이터 수정
foreach (int n in query)&amp;nbsp; &amp;nbsp; &amp;nbsp;// 이 시점에 실제 실행
&amp;nbsp; &amp;nbsp; Console.Write(n + &quot;|&quot;);&amp;nbsp; // 출력: 10|20|30|

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;query는 10, 20이 저장되는게 아니라 1, 2와 *10을 하는 메서드가 같이 저장이 되는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이후, 인자를 호출할때 메서드가 실행되어 호출되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;같은 쿼리를 단계별로 작성할 수도 있음:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

IEnumerable&amp;lt;string&amp;gt; filtered = names.Where(n =&amp;gt; n.Contains(&quot;a&quot;));
IEnumerable&amp;lt;string&amp;gt; sorted = filtered.OrderBy(n =&amp;gt; n.Length);
IEnumerable&amp;lt;string&amp;gt; finalQuery = sorted.Select(n =&amp;gt; n.ToUpper());

foreach (string name in filtered)
  Console.Write (name + &quot;|&quot;);        // Harry|Mary|Jay|

Console.WriteLine();
foreach (string name in sorted)
  Console.Write (name + &quot;|&quot;);        // Jay|Mary|Harry|

Console.WriteLine();
foreach (string name in finalQuery)
  Console.Write (name + &quot;|&quot;);        // JAY|MARY|HARRY|

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확장 메서드의 중요성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 메서드 구문을 사용하면 쿼리가 자연스럽게 왼쪽에서 오른쪽으로 흐름&lt;/li&gt;
&lt;li&gt;일반적인 정적 메서드로도 동일한 쿼리를 작성할 수 있으나 가독성이 떨어짐:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이게 메서드 체이닝을 사용하지 않은 경우다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 확장 메서드를 사용하지 않은 경우:
IEnumerable&amp;lt;string&amp;gt; query =
&amp;nbsp; &amp;nbsp; Enumerable.Select(
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Enumerable.OrderBy(
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Enumerable.Where(
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; names, n =&amp;gt; n.Contains(&quot;a&quot;)
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ), n =&amp;gt; n.Length
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ), n =&amp;gt; n.ToUpper()
&amp;nbsp; &amp;nbsp; );

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;람다 식 작성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;람다 식은 쿼리 연산자에 로직을 제공하는 방법임&lt;/li&gt;
&lt;li&gt;각 연산자마다 람다 식의 목적이 다름:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where: 요소를 포함할지 결정하는 조건 (bool 반환)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T형 매개변수를 받아야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OrderBy: 정렬 키 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 받으면 그 값을 기준으로 정렬되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Select: 각 요소의 변환 방법 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원하는 결과물의 데이터형을 람다식에 연결해서 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;람다 식과 타입 매개변수&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준 쿼리 연산자는 다음과 같은 타입 매개변수 명명 규칙을 사용함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TSource: 입력 시퀀스의 요소 타입&lt;/li&gt;
&lt;li&gt;TResult: 출력 시퀀스의 요소 타입 (TSource와 다른 경우)&lt;/li&gt;
&lt;li&gt;TKey: 정렬, 그룹화 또는 조인에 사용되는 키의 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Select 연산자의 타입 추론 예제&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; }; //TSource

// 문자열을 정수로 변환하는 예제
IEnumerable&amp;lt;int&amp;gt; query = names.Select(n =&amp;gt; n.Length); //TResult

foreach (int length in query)
&amp;nbsp; &amp;nbsp; Console.Write(length + &quot;|&quot;); // 3|4|5|4|3|

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TSource는 입력 시퀀스(names)로부터 string으로 결정됨&lt;/li&gt;
&lt;li&gt;TResult는 람다 식의 반환값(n.Length)으로부터 int로 추론됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Where 연산자의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where는 입력과 출력의 요소 타입이 동일함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요소를 필터링&lt;/b&gt;만 하고 변환하지 않기 때문임:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public static IEnumerable&amp;lt;TSource&amp;gt; Where&amp;lt;TSource&amp;gt;
&amp;nbsp; &amp;nbsp; (this IEnumerable&amp;lt;TSource&amp;gt; source, Func&amp;lt;TSource,bool&amp;gt; predicate)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OrderBy 연산자의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderBy는 정렬 키의 타입을 별도로 지정할 수 있음:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };
IEnumerable&amp;lt;string&amp;gt; sortedByLength, sortedAlphabetically;

sortedByLength = names.OrderBy(n =&amp;gt; n.Length);&amp;nbsp; &amp;nbsp; &amp;nbsp; // int 키 / int 정렬
sortedAlphabetically = names.OrderBy(n =&amp;gt; n);&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// string 키 / string 정렬

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;람다 식과 Func 시그니처&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준 쿼리 연산자는 제네릭 Func 대리자를 사용함&lt;/li&gt;
&lt;li&gt;Func의 타입 인자는 람다 식과 동일한 순서로 나타남:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Func&amp;lt;TSource,bool&amp;gt;는 TSource =&amp;gt; bool 람다 식과 일치&lt;/li&gt;
&lt;li&gt;Func&amp;lt;TSource,TResult&amp;gt;는 TSource =&amp;gt; TResult 람다 식과 일치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컴파일러는 람다 식의 반환값으로부터 TResult 타입을 자동으로 추론함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력 순서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 시퀀스의 &lt;b&gt;입력 순서&lt;/b&gt;가 LINQ에서 중요함&lt;/li&gt;
&lt;li&gt;Take, Skip, Reverse와 같은 연산자는 이 순서에 의존함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 3개는 LINQ의 연산자다.&lt;/li&gt;
&lt;li&gt;Take(count - 갯수) : 갯수만큼 반환하는 것이다.&lt;/li&gt;
&lt;li&gt;Skip(갯수) : 갯수만큼 건너뛰어 반환한다.&lt;/li&gt;
&lt;li&gt;Reverse() : 역순정렬&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] numbers = { 10, 9, 8, 7, 6 };

// 처음 3개 요소 선택
IEnumerable&amp;lt;int&amp;gt; firstThree = numbers.Take(3); // { 10, 9, 8 }

// 처음 3개를 제외한 나머지 선택
IEnumerable&amp;lt;int&amp;gt; lastTwo = numbers.Skip(3);&amp;nbsp; &amp;nbsp; // { 7, 6 }

// 순서 뒤집기
IEnumerable&amp;lt;int&amp;gt; reversed = numbers.Reverse();&amp;nbsp; // { 6, 7, 8, 9, 10 }

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타 연산자&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시퀀스를 반환하지 않는 연산자들도 있음&lt;/li&gt;
&lt;li&gt;요소 연산자: 입력 시퀀스에서 단일 요소를 추출함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;First() : 첫번쨰 요소 반환&lt;/li&gt;
&lt;li&gt;Last() : 마지막 요소 반환&lt;/li&gt;
&lt;li&gt;ElementAt(인덱스) : 인덱스의 요소 반환&lt;/li&gt;
&lt;li&gt;이렇게 LINQ를 쓰지 않는게 좋다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그냥 배열의 인덱스로 써서 호출해라&lt;/li&gt;
&lt;li&gt;인덱서가 없는 경우에만 사용하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] numbers = { 10, 9, 8, 7, 6 };

int firstNumber = numbers.First();&amp;nbsp; &amp;nbsp; &amp;nbsp;// 10
int lastNumber = numbers.Last();&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 6
int secondNumber = numbers.ElementAt(1); // 9

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집계 연산자: 보통 숫자 형식의 스칼라 값을 반환함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반환 데이터형이 IEnumerable이라면 그에 맞는 결과가 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;int count = numbers.Count();&amp;nbsp; // 5
int min = numbers.Min();&amp;nbsp; &amp;nbsp; &amp;nbsp; // 6

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한정자: bool 값을 반환함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션에 따라서 없는 컬렉션도 있으니 그때 사용하면 좋은 연산자들이다.&lt;/li&gt;
&lt;li&gt;Any : 비어있지 않으면 true&lt;/li&gt;
&lt;li&gt;Any(bool 반환 람다식) : 람다식이 true라면 true 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;bool hasTheNumberNine = numbers.Contains(9);&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// true
bool hasMoreThanZeroElements = numbers.Any();&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // true
bool hasAnOddElement = numbers.Any(n =&amp;gt; n % 2 != 0); // true

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 입력 시퀀스를 받는 연산자들도 있음:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Concat : 연결 함수 - 중복 허용&lt;/li&gt;
&lt;li&gt;Union : 합치는 함수 - 중복 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757410699900&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] seq1 = { 1, 2, 3 };
int[] seq2 = { 3, 4, 5 };

// 두 시퀀스 연결
IEnumerable&amp;lt;int&amp;gt; concat = seq1.Concat(seq2); // { 1, 2, 3, 3, 4, 5 }

// 중복 제거하며 연결
IEnumerable&amp;lt;int&amp;gt; union = seq1.Union(seq2);&amp;nbsp; &amp;nbsp;// { 1, 2, 3, 4, 5 }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부/C#</category>
      <category>지연 실행의 장점</category>
      <category>쿼리 연산자 체이닝</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/237</guid>
      <comments>https://thddlwnsrb.tistory.com/237#entry237comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:38:34 +0900</pubDate>
    </item>
    <item>
      <title>LINQ, 쿼리 연산자, 람다 식</title>
      <link>https://thddlwnsrb.tistory.com/236</link>
      <description>&lt;h1&gt;LINQ&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 메서드의 집합이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매개변수로 this가 들어가는 메서드다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CSV처럼 데이터 테이블의 정보를 LINQ 함수를 이용해서 가져오는것으로 사용한다.&lt;/li&gt;
&lt;li&gt;게임개발에서는 실시간으로 사용은 무리지만, 로딩시간이나 비 실시간에 한정으로 사용해도 괜찮다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LINQ의 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;LINQ (Language Integrated Query)&lt;/b&gt; 는 컬렉션에 대한 구조화된 타입 안전 쿼리를 작성하기 위한 C# 언어 및 .NET 런타임 기능임.&lt;/li&gt;
&lt;li&gt;배열, 리스트, XML DOM 등 IEnumerable&amp;lt;T&amp;gt;를 구현하는 모든 컬렉션 쿼리 가능.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;foreach의 인자인 컬렉션에 들어가는것처럼 사용할 수 있는것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컴파일 타임의 타입 검사와 동적 쿼리 구성의 장점을 제공함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LINQ의 장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코드 가독성 향상:&lt;/b&gt; 쿼리를 간결하고 직관적으로 표현 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생산성 향상:&lt;/b&gt; 데이터 쿼리 코드를 빠르고 쉽게 작성 가능.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LINQ 함수가 이미 정의되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일 타임 타입 검사:&lt;/b&gt; 컴파일 시점에 쿼리 오류를 검출하여 런타임 오류 방지 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 단위&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시퀀스 (Sequence):&lt;/b&gt; IEnumerable&amp;lt;T&amp;gt; 인터페이스를 구현하는 객체임.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;열거 가능한 컬렉션이다.&lt;/li&gt;
&lt;li&gt;foreach로 순회가 가능한 객체다.&lt;/li&gt;
&lt;li&gt;LINQ는 확장 메서드로 구현되어있다.&lt;/li&gt;
&lt;li&gt;즉, 확장메서드의 반환이 확장메서드가 아니라 인스턴스 메서드를 반환하는것처럼, 시퀀스도 자신을 반환할때 인스턴스 메서드를 반환하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요소 (Element):&lt;/b&gt; 시퀀스의 각 항목임.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 시퀀스 (Local sequence):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리에 이미 로드되어 있는 데이터 컬렉션을 의미함.&lt;/li&gt;
&lt;li&gt;배열, List&amp;lt;T&amp;gt;, Dictionary&amp;lt;K,V&amp;gt; 등 로컬 컬렉션에 대한 쿼리를 &lt;b&gt;LINQ to Objects&lt;/b&gt; 쿼리라고 함.&lt;/li&gt;
&lt;li&gt;로컬 시퀀스 쿼리는 즉시 실행 가능하며 네트워크 지연이 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System.LINQ;

// 로컬 시퀀스의 예
string[] namesArray = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot; }; // 배열
List&amp;lt;string&amp;gt; namesList = new List&amp;lt;string&amp;gt; { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot; }; // 리스트
Dictionary&amp;lt;int, string&amp;gt; namesDict = new Dictionary&amp;lt;int, string&amp;gt; // 딕셔너리
{
	{ 1, &quot;Tom&quot; },
	{ 2, &quot;Dick&quot; },
	{ 3, &quot;Harry&quot; }
};

// 각각의 컬렉션에 대한 LINQ 쿼리 (플루언트 구문)
var arrayQuery = namesArray.Where(n =&amp;gt; n.Length &amp;gt; 3); // 길이가 3보다 큰 요소만 선택
var listQuery = namesList.Where(n =&amp;gt; n.Length &amp;gt; 3);  // 길이가 3보다 큰 요소만 선택
var dictQuery = namesDict.Where(kvp =&amp;gt; kvp.Value.Length &amp;gt; 3); // 값이 3보다 큰 요소만 선택

// 결과 출력
foreach (var name in arrayQuery)
	Console.WriteLine(name); // Dick, Harry
foreach (var name in listQuery)
	Console.WriteLine(name);  // Dick, Harry
foreach (var kvp in dictQuery)
	Console.WriteLine($&quot;{kvp.Key}: {kvp.Value}&quot;); // 2: Dick, 3: Harry

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 연산자 (Query Operator)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 연산자는 시퀀스를 변환하는 &lt;b&gt;메서드&lt;/b&gt;임.&lt;/li&gt;
&lt;li&gt;일반적으로 입력 시퀀스를 받아 변환된 출력 시퀀스를 반환함.&lt;/li&gt;
&lt;li&gt;System.Linq의 Enumerable 클래스에는 약 40개의 표준 쿼리 연산자가 정의되어 있음.&lt;/li&gt;
&lt;li&gt;모든 연산자는 &lt;b&gt;정적 확장 메서드로 구현&lt;/b&gt;됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot; };
// System.Linq.Enumerable.Where() 직접 호출
IEnumerable&amp;lt;string&amp;gt; filteredNames = System.Linq.Enumerable.Where(
	names, n =&amp;gt; n.Length &amp;gt;= 4);

foreach (string n in filteredNames)
	Console.WriteLine(n);

// 출력:
// Dick
// Harry

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준 쿼리 연산자는 확장 메서드로 구현되어 있어서 names에서 직접 Where를 호출할 수 있음. (플루언트 구문)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot; };
// 확장 메서드를 사용한 Where() 호출 (플루언트 구문)
IEnumerable&amp;lt;string&amp;gt; filteredNames = names.Where(n =&amp;gt; n.Length &amp;gt;= 4);

foreach (string name in filteredNames)
	Console.WriteLine(name);

// 출력:
// Dick
// Harry

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;람다 식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 쿼리 연산자는 람다 식을 인자로 받음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;람다 식만 쓰이는것은 아니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 메서드도 참조 인자로 넘길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;람다 식은 쿼리의 형태를 결정하는데 도움을 줌.&lt;/li&gt;
&lt;li&gt;Where 연산자는 람다 식이 bool 값을 반환하도록 요구함.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot; };
// Where() 연산자에 람다 식 사용
IEnumerable&amp;lt;string&amp;gt; filteredNames = names.Where(n =&amp;gt; n.Contains(&quot;a&quot;));

foreach (string name in filteredNames)
	Console.WriteLine(name); // Harry

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;filteredNames를 타입 추론을 사용하여 더 간단히 작성할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;var filteredNames = names.Where(n =&amp;gt; n.Length &amp;gt;= 4);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Where 연산자의 시그니처&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where 연산자의 형식은 다음과 같음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public static IEnumerable&amp;lt;TSource&amp;gt; Where&amp;lt;TSource&amp;gt;(
	this IEnumerable&amp;lt;TSource&amp;gt; source, //확장메서드 매개변수
	Func&amp;lt;TSource, bool&amp;gt; predicate) //펑션 델리게이트

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TSource: 입력 시퀀스의 요소 타입&lt;/li&gt;
&lt;li&gt;predicate: 각 요소를 bool 값으로 매핑하는 델리게이트&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 작성 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C#은 쿼리를 작성하기 위한 두 가지 방법을 제공함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;플루언트 구문 (Fluent Syntax):&lt;/b&gt; 확장 메서드와 람다 식을 사용함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿼리 구문 (Query Syntax):&lt;/b&gt; SQL과 유사한 구문임.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB의 쿼리문을 흉내낸것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;from절부터 select 절로 끝나는 구문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2가지의 구문을 섞어서 쓸 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿼리 구문과 플루언트 구문의 장단점 비교:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징 쿼리 구문 플루언트 구문&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;가독성&lt;/td&gt;
&lt;td&gt;좋음 (특히 SQL에 익숙한 경우)&lt;/td&gt;
&lt;td&gt;쿼리 구문보다 떨어질 수 있음 (특히 복잡한 쿼리의 경우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유연성&lt;/td&gt;
&lt;td&gt;낮음 (복잡한 쿼리 표현에 한계)&lt;/td&gt;
&lt;td&gt;높음 (모든 LINQ 쿼리를 표현 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기능&lt;/td&gt;
&lt;td&gt;일부 LINQ 기능만 지원&lt;/td&gt;
&lt;td&gt;모든 LINQ 기능 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컴파일러 변환&lt;/td&gt;
&lt;td&gt;플루언트 구문으로 변환됨&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 상황&lt;/td&gt;
&lt;td&gt;간단하고 직관적인 쿼리&lt;/td&gt;
&lt;td&gt;복잡하거나 동적인 쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;

string[] names = { &quot;Tom&quot;, &quot;Dick&quot;, &quot;Harry&quot;, &quot;Mary&quot;, &quot;Jay&quot; };

// 쿼리 구문 (Query Syntax)
var querySyntaxResult = from n in names
					   where n.Contains(&quot;a&quot;) // &quot;a&quot;를 포함하는 요소 선택
					   orderby n             // 오름차순 정렬
					   select n.ToUpper();    // 대문자로 변환

// 플루언트 구문 (Fluent Syntax) - 가독성 및 편의성이 좋다.
var fluentSyntaxResult = names
						 .Where(n =&amp;gt; n.Contains(&quot;a&quot;)) // &quot;a&quot;를 포함하는 요소 선택
						 .OrderBy(n =&amp;gt; n)            // 오름차순 정렬
						 .Select(n =&amp;gt; n.ToUpper());   // 대문자로 변환

// 결과 출력 (두 구문 모두 동일한 결과)
Console.WriteLine(&quot;쿼리 구문 결과:&quot;);
foreach (string name in querySyntaxResult)
	Console.WriteLine(name); // HARRY, JAY, MARY

Console.WriteLine(&quot;\\\\n플루언트 구문 결과:&quot;);
foreach (string name in fluentSyntaxResult)
	Console.WriteLine(name); // HARRY, JAY, MARY
	
//결과가 바뀐다.
name[0 = 'AAAA&quot;;

foreach (string name in fluentSyntaxResult)
	Console.WriteLine(name); // HARRY, JAY, MARY

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 구문의 구성:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;from 절: 범위 변수(range variable)를 선언하며, foreach 문의 반복 변수와 유사함.&lt;/li&gt;
&lt;li&gt;select 절: &lt;b&gt;결과값을 선택&lt;/b&gt;하며, 모든 쿼리 식은 select나 group으로 끝나야 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출력하는 결과값을 어떻게 반환할지 결정하는 함수다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컴파일러는 쿼리 구문을 플루언트 구문으로 변환함.&lt;/li&gt;
&lt;li&gt;플루언트 단점 ; 변수를 생성하기에 가비지 컬렉션의 재상이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 쿼리 구문:
from n in names
where n.Contains(&quot;a&quot;)
select n;

// 컴파일러가 변환한 플루언트 구문:
names.Where(n =&amp;gt; n.Contains(&quot;a&quot;))

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;System.Linq 네임스페이스를 임포트해야 쿼리가 컴파일됨.&lt;/li&gt;
&lt;li&gt;쿼리 구문은 SQL에서 영감을 받았지만, C# 식으로 변환되어 C#의 표준 규칙을 따름.&lt;/li&gt;
&lt;li&gt;SQL과 달리 변수를 선언하기 전에 사용할 수 없으며, 데이터는 왼쪽에서 오른쪽으로 논리적으로 흐름.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/C#</category>
      <category>LINQ</category>
      <category>람다 식</category>
      <category>쿼리 연산자</category>
      <category>쿼리 연산자 체이닝</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/236</guid>
      <comments>https://thddlwnsrb.tistory.com/236#entry236comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:35:51 +0900</pubDate>
    </item>
    <item>
      <title>오디오 믹서, 카메라 제한, 총 데이터, 총, 슈터</title>
      <link>https://thddlwnsrb.tistory.com/235</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마우스의 포지션을 월드포지션으로 변환 &amp;rarr; 바닥과의 충돌을 검사&lt;/li&gt;
&lt;li&gt;유니티에서 카메라를 비출때 카메라와 월드 사이에는 &amp;lsquo;투영면&amp;rsquo;이 하나 보이지않게 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 화면이 사용자가 마우스로 클릭되는 화면이다.&lt;/li&gt;
&lt;li&gt;마우스 클릭 후 투영면에는 점이 하나 생기고 그곳부터 z축으로 방향이 하나 생기는 것이다.&lt;/li&gt;
&lt;li&gt;해당 방향으로 레이캐스트를 쏘면 3D 상에 충돌하는 점이 3D상의 좌표가 되는 것이다.&lt;/li&gt;
&lt;li&gt;그런데, 이렇게하면 화면상의 좌표계와 카메라상의 좌표계가 맞지 않게된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카메라가 사선으로 보고있을수도 있고, 카메라가 회전했다면 월드 좌표의 회전과 맞지 않을수 있기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해결 방법 : Transform에 좌표계를 스크린좌표 &amp;harr; 월드좌표를 변환하는 함수가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스크린 좌표를 월드 좌표로 변환하는 과정을 거쳐야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(cameraRay, out RaycastHit hit, float.PositiveInfinity, groundMask))
{
    Vector3 pos = hit.point;
    pos.y = transform.position.y;
    transform.LookAt(pos);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;오디오 믹서&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bgm mixer&lt;/li&gt;
&lt;li&gt;sfx mixer&lt;/li&gt;
&lt;li&gt;믹서들로 브금, 효과음 별로 그룹을 엮어서 슬라이더를 이용하여 볼륨을 설정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public AudioMixer audioMixer;
public Slider bgmSlider;
public Slider sfxSlider;

public void SetMusicVolume(float volume)
{
    audioMixer.SetFloat(&quot;Music&quot;, Mathf.Log10(volume) * 20);
}

public void SetSFXVolume(float volume)
{
    audioMixer.SetFloat(&quot;SFX&quot;, Mathf.Log10(volume) * 20);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;카메라 제한&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cinemachine Comfinder 3D로 벽 밖으로 나가지 못하게 제한할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;총 데이터&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발사 소리&lt;/li&gt;
&lt;li&gt;데미지&lt;/li&gt;
&lt;li&gt;발사 간격 : timeBetFire&lt;/li&gt;
&lt;li&gt;사정 거리 : fireDistance&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[CreateAssetMenu(fileName = &quot;GunData&quot;, menuName = &quot;Scriptable Objects/GunData&quot;)]
public class GunData : ScriptableObject
{
    public AudioClip shootClip;

    public float damage = 25f;
    public float timeBetFire = 0.12f;
    public float fireDistance = 50f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;총&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태&lt;/li&gt;
&lt;li&gt;현재 상태&lt;/li&gt;
&lt;li&gt;총 데이터&lt;/li&gt;
&lt;li&gt;이펙트&lt;/li&gt;
&lt;li&gt;라인 렌더러&lt;/li&gt;
&lt;li&gt;오디오&lt;/li&gt;
&lt;li&gt;발사 위치&lt;/li&gt;
&lt;li&gt;마지막 발사 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;슈터&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 스크립트&lt;/li&gt;
&lt;li&gt;리지드 바디&lt;/li&gt;
&lt;li&gt;총 콜라이더&lt;/li&gt;
&lt;li&gt;인풋&lt;/li&gt;
&lt;li&gt;총 위치&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/Unity</category>
      <category>바닥 충돌</category>
      <category>오디오 믹서</category>
      <author>월러비</author>
      <guid isPermaLink="true">https://thddlwnsrb.tistory.com/235</guid>
      <comments>https://thddlwnsrb.tistory.com/235#entry235comment</comments>
      <pubDate>Tue, 9 Sep 2025 18:29:47 +0900</pubDate>
    </item>
  </channel>
</rss>