<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Cadi</title>
    <link>https://febelo0524.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 06:43:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Cadi</managingEditor>
    <item>
      <title>Reflection</title>
      <link>https://febelo0524.tistory.com/306</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Reflection이란 ?&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 개념&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션(Reflection)은 '거울에 비친다'는 뜻을 담고 있다. 이를 코딩에서 적용하자면 프로그램이 실행중인 상황, 즉 런타임에 자신의 메타데이터를 거울로 보듯 들여다 본다는 뜻이다. 동적으로 객체 내부 구조를 검사하고, 조작할 수 있게 하는 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 원리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일된 DC# 프로그램 (DLL 파일 등)에는 메타데이터가 기록되어 있다.&amp;nbsp;&lt;br /&gt;리플랙션은 System.Reflection 네임스페이스에 있는 클래스들 (FieldInfo, MethodInfo 등) 을 사용해 메타 데이터를 스캔한다.&lt;br /&gt;추가적으로 메타데이터를 기반으로 Private과 같은 변수/함수들도 값을 바꾸거나 특정 함수를 강제로 실행시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 간단한 예시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774516472973&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Type targetType = typeof(Monster);

// Monster 설계도 안에 있는 &quot;Attack&quot;이라는 이름의 함수 정보를 스캔해서 가져옵니다.
MethodInfo attackMethod = targetType.GetMethod(&quot;Attack&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Reflection의 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774516554075&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;특성 및 리플렉션 - C#&quot; data-og-description=&quot;특성을 사용하여 C#에서 메타데이터 또는 선언적 정보를 연결합니다. 어셈블리, 모듈 및 형식을 설명하는 리플렉션 API를 사용하여 런타임에 특성을 쿼리합니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/htkiQ/dJMb8XR5nl2/kVks8iaIRWu490CU6GpkA0/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/htkiQ/dJMb8XR5nl2/kVks8iaIRWu490CU6GpkA0/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;특성 및 리플렉션 - C#&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;특성을 사용하여 C#에서 메타데이터 또는 선언적 정보를 연결합니다. 어셈블리, 모듈 및 형식을 설명하는 리플렉션 API를 사용하여 런타임에 특성을 쿼리합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 공부하지 않은 용어들이 많이 나와 차후에 작성 예정.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Unity에서의 Reflection 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 가장 많이 쓰이는 어트리뷰트인 [SerializeField]를 예시로 들어보자.&amp;nbsp;&lt;br /&gt;이 어트리뷰트는, Private이지만 인스펙터에서 조정가능하게 해달라는 메타 데이터를 닮고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티 에디터는 내부적으로 리플렉션을 사용해 인스펙터 창을 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;동작 과정을 간략화한 코드 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1774519541452&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Reflection;

public class Player 
{
    // private이라서 원래는 외부에서 접근할 수 없다. 
    // SerialzieField라는 어트리뷰트를 붙여줘 인스펙터에 보이게 된다. 
    [SerializeField]
    private int _hp = 100; 
}

// 내부 리플렉션 과정(예시)
public class UnityEditorMock
{
    public void DrawInspector(object targetObject)
    {
        Type targetType = targetObject.GetType();

        // BindingFlags를 사용해 private(NonPublic) 변수까지 모두 스캔합니다.
        BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
        FieldInfo[] fields = targetType.GetFields(flags);

        foreach (FieldInfo field in fields)
        {
            // 해당 변수에 [SerializeField] 포스트잇이 붙어있는지 확인합니다.
            var attribute = field.GetCustomAttribute&amp;lt;SerializeField&amp;gt;();
            
            if (attribute != null)
            {
                // 포스트잇이 있다면, 리플렉션을 이용해 private 변수임에도 현재 값을 읽어옵니다.
                object currentValue = field.GetValue(targetObject);
                Console.WriteLine($&quot;인스펙터에 노출할 변수 발견: {field.Name}, 현재 값: {currentValue}&quot;);
                
                // 만약 유니티 인스펙터 창에서 사용자가 값을 200으로 수정했다면?
                // SetValue를 통해 private 변수의 값을 강제로 변경합니다.
                field.SetValue(targetObject, 200); 
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 실제로는&lt;/p&gt;
&lt;pre id=&quot;code_1774519908833&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;namespace UnityEngine
{
    [RequiredByNativeCode]
    public sealed partial class SerializeField : Attribute
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 되어 있어 AI의 도움을 받았다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Reflection의 장/단점&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유연성 : 이름만 알면 런타임에 제어가 가능하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;확장성 및 자동화 : 유니티의 인스펙터 커스텀 에디터, 직렬화, 의존성 주입 등 강력한 프레임워크와 자동화 툴을 만드는데 필수적이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;-&amp;gt; 물론 , 매 프레임 GetType을 사용해 이름으로 설계도를 찾는 것은 성능상 큰 문제겠지만 1초에 한 두 번이나 로딩, 초반부에 하는 것은 성능상 큰 문제가 발생하지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&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#에서 중요하게 생각하는 것들 중 하나는 캡슐화이다. 다만 리플렉션을 무분별하게 사용하면 이를 해함.&lt;/li&gt;
&lt;li&gt;유지보수 위험성 : GetField(&quot;이름&quot;) 처럼 문자열에 의존하기 때문에, 유지보수가 어렵다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념공부</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/306</guid>
      <comments>https://febelo0524.tistory.com/306#entry306comment</comments>
      <pubDate>Thu, 26 Mar 2026 19:27:28 +0900</pubDate>
    </item>
    <item>
      <title>Attribute</title>
      <link>https://febelo0524.tistory.com/305</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Attribute란 ?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attribute에 대해 알기 위해선 우선 '메타 데이터'라는 개념에 대해 알아야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 메타데이터&lt;/b&gt;란 '데이터를 설명하는 데이터'를 뜻한다. 우리가 스마트폰으로 사진을 찍으면 사진(이미지 데이터) 파일 안에 촬영 날짜, 위치, 카메라 기종 같은 정보가 몰래 숨어있는데, 이것이 바로 메타데이터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 프로그래밍에서는 코드를 컴파일 할 때, 클래스명은 무엇인지, 어떤 변수와 함수를 가졌는지 등의 구조 정보를 전부 모아서 DLL 파일 안에 기록해둔다. 전 포스팅에서 다뤘던 'Type(설계도)' 정보 자체가 C# 프로그래밍의 가장 기본적인 메타데이터인&amp;nbsp; 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Attribute&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attribute는 이렇게 기본적으로 생성되는 메타데이터 이외에 개발자가 추가로 붙이는 포스트잇이라고 생각하면 편하다.&amp;nbsp;&lt;br /&gt;설계도 자체나 , 특정 부품 (변수 / 함수 ) 앞에 &quot;이렇게 다뤄주세요&quot;라고 메모를 남기는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attribute의 가장 중요한 특성은 수동성이다. 포스트잇이라고 말한 것에서 알 수 있듯이, 이를 붙인다고 하여 클래스나 함수의 본래 기능이나 로직 자체가 변하지는 않는다. 오직 누군가 읽고 참고하라고 남겨두는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Attribute는 어떻게 저장되어 있는가 ?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0&quot;&gt;프로그램이 실행될 때&lt;/b&gt; 어트리뷰트는 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;13,0&quot;&gt;자동으로&lt;/b&gt; 객체로 메모리에 올라가지 않는다. 컴파일러가 코드를 번역할 때, &quot;여기에 포스트잇이 붙어 있음&quot;이라는 텍스트 형태의 메타데이터로 변환되어 파일(DLL) 안에 기록될 뿐이다. 나중에 &lt;b data-index-in-node=&quot;129&quot; data-path-to-node=&quot;13,0&quot;&gt;리플렉션(Reflection)을 통해&lt;/b&gt; &quot;포스트잇에 뭐라고 적혀 있는지 확인해봐!&quot;라고 &lt;b data-index-in-node=&quot;177&quot; data-path-to-node=&quot;13,0&quot;&gt;요청하는 순간에만 비로소 메모리에 객체(Instance)로 생성되어&lt;/b&gt; 우리에게 정보를 전달해 준다. 그 전까지는 하드디스크 속의 정적인 텍스트 데이터일 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 자주 쓰는 Attribute들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[SerializeField] : 유니티 에디터에게 내리는 명령. &quot;이 변수는 private이라 외부에서 못 보지만, 유니티 인스펙터 창에서는 예외적으로 값을 수정할 수 있게 노출해 줘!&quot;&lt;/li&gt;
&lt;li&gt;[RequireComponent(typeof(Rigidbody))] : 유니티 에디터와 런타임에 보내는 경고. &quot;이 스크립트를 게임 오브젝트에 붙이려면 무조건 Rigidbody가 필요해! 없으면 네가 알아서 추가해 줘.&quot;&lt;/li&gt;
&lt;li&gt;[Obsolete(&quot;이 함수는 구버전입니다.&quot;)] : C# 컴파일러와 동료 개발자에게 남기는 메모. &quot;이 함수는 더 이상 쓰지 마. 쓰면 컴파일 창에 노란색 경고(Warning)를 띄울 거야.&quot;&lt;/li&gt;
&lt;li&gt;[Header(&quot;Player Stats&quot;)], [Space] : 유니티 인스펙터 창의 UI를 예쁘게 꾸며달라고 에디터에게 요청하는 장식용 포스트잇.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. CustomAttribute&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 제공되는 Attribute 이외에도 Attirbute를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.Attribute 클래스를 상속받기만 하면 만들어진다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774513026407&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;

// 관례적으로 이름 끝에 'Attribute'를 붙인다.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class InteractableAttribute : Attribute
{
    public string Description;

    // 생성자
    public InteractableAttribute(string desc)
    {
        Description = desc;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 사용한 AttributeUsage는 &quot;어디에 붙일 수 있는지&quot; 에 관한 것이다.&lt;br /&gt;예를 들어 위와 같이 (AttributeTargets.Class | AttributeTargets.Method) 라고 사용했다면 클래스와 함수에만 붙일 수 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그리고 만들고 사용할 때 주의 사항은 다음과 같다.&lt;br /&gt;1. 관례적으로 이름 끝에 Attribute를 붙인다.&lt;/p&gt;
&lt;pre id=&quot;code_1774513443014&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 어트리뷰트 정의
public class InteractableAttribute : Attribute
{
    // 생성자로 받는 필수 값
    public string Description; 
    
    // 선택적으로 넣을 수 있는 public 필드 (또는 프로퍼티)
    public int Priority = 0; 
    public bool IsOneTimeUse = false;

    public InteractableAttribute(string desc)
    {
        Description = desc;
    }
}

// 어트리뷰트 사용
// 1. 필수 값만 넣기
[Interactable(&quot;평범한 문&quot;)]
public class NormalDoor { }

// 2. 필수 값 + 프로퍼티(선택적 값) 함께 넣기
[Interactable(&quot;황금 보물상자&quot;, Priority = 99, IsOneTimeUse = true)]
public class GoldenBox { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용할 때에는 Attribute를 붙이지 않고 사용할 수 있다. ( EX: [Interactable(&quot;설명&quot;)]&lt;br /&gt;3. 매개변수가 있는 생성자를 갖고 있는 Attribute일 때에는 매개변수를 넣어준다. ( EX: [Interactable(&quot;설명&quot;) )&lt;br /&gt;4. 커스텀 어트리뷰트 클래스 안에 public으로 열려있는 변수(필드)나 프로퍼티가 있다면, 필수 값을 먼저 넣은 뒤에 &lt;b data-index-in-node=&quot;112&quot; data-path-to-node=&quot;20,4&quot;&gt;이름 = 값&lt;/b&gt; 형태로 추가적인 데이터를 쏙쏙 골라서 넣을 수 있다. (순서 상관없음) &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774513735458&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;자습서: 사용자 지정 특성을 정의하고 읽습니다. - C#&quot; data-og-description=&quot;C#에서 특성이 작동하는 방식을 알아봅니다. 사용자 지정 특성을 정의하여 코드에 메타데이터를 추가합니다. 이러한 특성을 읽고 런타임 시 코드에 대해 알아봅니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOEs33/dJMb8XR5m2R/LGW6sld9SKoHwL6pDETid1/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOEs33/dJMb8XR5m2R/LGW6sld9SKoHwL6pDETid1/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;자습서: 사용자 지정 특성을 정의하고 읽습니다. - C#&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;C#에서 특성이 작동하는 방식을 알아봅니다. 사용자 지정 특성을 정의하여 코드에 메타데이터를 추가합니다. 이러한 특성을 읽고 런타임 시 코드에 대해 알아봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;이러한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Attribute&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;개체는 지연 초기화된다는 점에 유의해야 합니다. 즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GetCustomAttribute&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GetCustomAttributes&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;를 사용할 때까지 인스턴스화되지 않습니다. 또한 매번 인스턴스화됩니다. 행에서 두 번 호출&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GetCustomAttributes&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;하면 두 개의 서로 다른 인스턴스가 반환됩니다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ObsoleteAttribute&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;. 라는 문장이 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하면서 적은 내용이기 때문에 틀릴 수 있습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Attribute</category>
      <category>C#</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/305</guid>
      <comments>https://febelo0524.tistory.com/305#entry305comment</comments>
      <pubDate>Thu, 26 Mar 2026 17:29:15 +0900</pubDate>
    </item>
    <item>
      <title>C# Type (0327 수정)</title>
      <link>https://febelo0524.tistory.com/304</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reflection과 Attribue를 공부하다가 이것저것 더 알아보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;1. Type이란?&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;게임에서 몬스터를 만든다고 가정해 보자. 우리는 다음과 같은 코드로 몬스터를 만든다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjlxJu1_LyTAxUAAAAAHQAAAAAQoQE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Monster A = new Monster();
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이때 생성된 A는 메모리 공간을 차지하고 살아 숨 쉬는 &lt;b&gt;'객체(Instance)'&lt;/b&gt;이다.&amp;nbsp;&lt;br /&gt;그렇다면 이 객체를 만들어내기 위한 설계도(Type)란 정확히 무엇일까?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;① 모든 것은 '메타데이터(Metadata)'로 기록된다&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;C#에서 코드를 작성하고 컴파일(빌드)을 하면, 컴퓨터는 소스 코드를 기계어로 번역함과 동시에 메타데이터(Metadata)'라는 거대한 정보 사전을 만들어낸다. 이 메타데이터 안에는 프로그램에 존재하는 모든 클래스, 구조체, 인터페이스, 열거형(Enum) 등의 이름, 변수(Field), 함수(Method), 상속 관계가 빠짐없이 기록된다. 즉, C#에서 말하는 Type이란 &lt;b data-index-in-node=&quot;272&quot; data-path-to-node=&quot;10&quot;&gt;컴파일러가 만들어낸 이 메타데이터(설계도) 정보를 프로그램이 실행되는 중(런타임)에 코드로 읽고 다룰 수 있도록 객체화해 놓은 것&lt;/b&gt;을 의미한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;② 100마리의 몬스터, 단 1개의 설계도&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 new Monster()를 100번 호출해서 100마리의 몬스터 객체를 만들었다고 해보자. 메모리에는 100개의 몬스터 실체가 올라가지만, &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;11&quot;&gt;설계도(Type 객체)는 메모리에 단 1개만 존재&lt;/b&gt;한다. 100마리의 몬스터는 모두 이 유일한 원본 설계도 하나를 함께 가리키고(참조하고) 있는 형태다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;③ C#의 모든 것은 Type을 가진다&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;&lt;br /&gt;&lt;/b&gt; &lt;br /&gt;그렇다면 특정 클래스나 구조체만 설계도를 가질까? 그렇지 않다. C#은 '강한 타입(Strongly typed) 언어'이기 때문에, 복잡한 클래스는 물론 int나 float 같은 기본 자료형까지 &lt;b data-index-in-node=&quot;151&quot; data-path-to-node=&quot;12&quot;&gt;C#에 존재하는 모든 데이터는 반드시 자신의 Type을 가져야만 한다.&lt;/b&gt; 이를 시스템적으로 보장하기 위해 C#의 모든 데이터는 System.Object라는 최고 조상으로부터 파생되도록 설계되었다. 그리고 이 Object 클래스 안에는 자신의 원본 설계도를 찾아 반환해 주는 GetType()이라는 기능이 기본적으로 내장되어 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 C#의 모든 것들은 &quot;너의 설계도가 무엇이냐?&quot;라는 질문을 받았을 때, 언제든지 System.Type 객체를 꺼내어 대답할 수 있는 능력을 갖추고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774508082302&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Type 클래스 (System)&quot; data-og-description=&quot;클래스 형식, 인터페이스 형식, 배열 형식, 값 형식, 열거형 형식, 형식 매개 변수, 제네릭 형식 정의 및 개방형 생성 제네릭 형식이나 폐쇄형 생성 제네릭 형식에 대한 형식 선언을 나타냅니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcR5qd/dJMb87f6aQf/0p7aOycQ1k3y3uKqSCeKU0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcR5qd/dJMb87f6aQf/0p7aOycQ1k3y3uKqSCeKU0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Type 클래스 (System)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;클래스 형식, 인터페이스 형식, 배열 형식, 값 형식, 열거형 형식, 형식 매개 변수, 제네릭 형식 정의 및 개방형 생성 제네릭 형식이나 폐쇄형 생성 제네릭 형식에 대한 형식 선언을 나타냅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #282a2c; color: #e3e3e3; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&quot;&gt;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774508089565&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Learn the fundamentals of the C# type system - C#&quot; data-og-description=&quot;Learn about creating types in C#, such as tuples, records, value types, and reference types. Learn to choose between these options.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&quot; data-og-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFkgco/dJMb9b3RK46/Vo30wcQJddUFiLggCwy9q0/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFkgco/dJMb9b3RK46/Vo30wcQJddUFiLggCwy9q0/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Learn the fundamentals of the C# type system - C#&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn about creating types in C#, such as tuples, records, value types, and reference types. Learn to choose between these options.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설계도 읽기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 방법으로 설계도를 읽을 수 있다.&lt;br /&gt;&lt;br /&gt;1.&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt; typeof(클래스명) : 대상을 정확히 알고 있을 때 (컴파일 타임)&lt;/b&gt; &lt;br /&gt;객체를 직접 생성하지 않고, 클래스의 이름만으로 설계도를 바로 가져오고 싶을 때 사용, &lt;br /&gt;코드를 작성하는 시점에 대상이 무엇인지 명확하게 알고 있을 때 주로 쓰임.&lt;/p&gt;
&lt;pre id=&quot;code_1774508769484&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인스턴스 없이, Player 클래스 자체의 설계도를 바로 가져옵니다.
Type playerType = typeof(Player);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt; 변수.GetType() : 대상의 진짜 정체를 모를 때 (런타임)&lt;/b&gt; &lt;br /&gt;눈앞에 생성된 실체(인스턴스)가 정확히 어떤 클래스로 만들어졌는지 모를 때, &lt;br /&gt;그 객체에게 직접 &quot;너의 설계도를 줘!&quot;라고 요청하는 방법.&lt;/p&gt;
&lt;pre id=&quot;code_1774508794203&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// target이 오크인지 고블린인지 모를 때, 실제 생성된 객체의 설계도를 뽑아옵니다.
public void Attack(object target)
{
    Type targetType = target.GetType(); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 궁금했던 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;Q1. C#에서 말하는 Type은 정확히 무엇인가요? 작성한 코드 자체와 같은 건가요?&lt;/b&gt; &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;3&quot;&gt;A.&lt;/b&gt; 그렇지 않습니다. Type은 코드도, 메모리에 올라간 실제 데이터(인스턴스)도 아닙니다. 비유하자면 &lt;b data-index-in-node=&quot;108&quot; data-path-to-node=&quot;3&quot;&gt;'클래스의 설계 사양서'&lt;/b&gt; 또는 **'목차'**와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;포함되는 정보 (메타데이터):&lt;/b&gt; 클래스의 이름, 가지고 있는 변수명과 데이터 타입(int, string 등), 함수의 이름과 매개변수 구조 등 껍데기 정보.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;포함되지 않는 정보:&lt;/b&gt; 변수에 들어있는 실제 값(예: hp가 100인지 50인지), 함수의 내부 실행 로직, 특정 객체의 메모리 주소 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Q2. Type에 객체의 실제 값이 없다면, 유니티 인스펙터 창에서는 어떻게 변수 값을 보여주고 수정할 수 있는 건가요?&lt;/b&gt; &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;5&quot;&gt;A.&lt;/b&gt; 유니티 에디터는 **Type(설계도)**과 &lt;b data-index-in-node=&quot;95&quot; data-path-to-node=&quot;5&quot;&gt;Instance(실제 객체)&lt;/b&gt; 두 가지를 모두 활용하여 인스펙터를 구성합니다. (리플렉션 및 직렬화 과정)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;UI 생성 (Type 활용):&lt;/b&gt; C# 스크립트의 Type 정보를 읽어와 &quot;어떤 타입의 변수들이 있는지&quot; 파악하고, 그에 맞는 입력칸(UI)을 화면에 그립니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;값 갱신 (Instance 활용):&lt;/b&gt; 화면에 띄울 빈칸을 만든 후, 현재 씬에서 선택된 실제 게임 오브젝트(Instance)의 메모리에 직접 접근하여 현재 값을 읽어와 빈칸을 채웁니다. 인스펙터에서 값을 수정하면 에디터가 다시 해당 메모리 주소로 찾아가 값을 덮어씌웁니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;Q3. GetComponent&amp;lt;T&amp;gt;()는 내부적으로 typeof를 사용한다고 했는데, 그럼 검색 비용이 드는 건가요? 씬 전체를 뒤지는 건가요?&lt;/b&gt; &lt;b data-index-in-node=&quot;81&quot; data-path-to-node=&quot;7&quot;&gt;A.&lt;/b&gt; 네, 명백하게 &lt;b data-index-in-node=&quot;92&quot; data-path-to-node=&quot;7&quot;&gt;무거운 검색 비용이 발생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;typeof(T)와의 차이:&lt;/b&gt; typeof(T) 자체는 단순히 메모리에 있는 Type 사양서를 가리키는 명령어라 비용이 거의 없습니다. 하지만 GetComponent&amp;lt;T&amp;gt;()는 이 Type 정보를 '검색 키워드'로 사용하여 실제 일치하는 컴포넌트를 찾아내는 '동작'입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;검색 범위와 과정:&lt;/b&gt; 씬 전체가 아닌 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;8,1,0&quot;&gt;해당 게임 오브젝트 내부&lt;/b&gt;에서만 검색합니다. 오브젝트가 가진 컴포넌트 배열을 순회하며, C++ 코어 엔진과 C# 환경을 오가면서 각 컴포넌트의 Type이 요청한 Type과 일치하는지 일일이 대조합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;결론:&lt;/b&gt; 이 과정에서 CPU 자원이 소모되므로, Update() 같은 반복 함수에서 매 프레임 호출하면 프레임 드랍의 원인이 됩니다. 반드시 Start()나 Awake()에서 한 번만 호출하여 변수에 캐싱(저장)해 두고 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개념공부</category>
      <category>C#</category>
      <category>type</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/304</guid>
      <comments>https://febelo0524.tistory.com/304#entry304comment</comments>
      <pubDate>Thu, 26 Mar 2026 17:03:46 +0900</pubDate>
    </item>
    <item>
      <title>C# 프로퍼티(Property)</title>
      <link>https://febelo0524.tistory.com/303</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티는 외부에서는 일반 &lt;b&gt;'변수(Field)'&lt;/b&gt;처럼 보이지만, 실제로는 내부적으로 &lt;b&gt;'메서드(접근자)'&lt;/b&gt;처럼 동작하는 C#의 강력한 기능입니다. 데이터의 안전을 지키면서도 사용의 편의성을 제공하는 '똑똑한 변수'라고 할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 일반 변수(Field)의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 유니티에서 변수를 선언할 때 다음과 같이 작성하는 경우가 많습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 일반적인 필드(Field) 방식
public float speed;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변수를 public으로 열어두면, 외부의 다른 클래스에서 이 변수에 접근해 값을 마음대로 오염(잘못된 값 대입)시킬 수 있는 치명적인 위험이 존재합니다. 프로퍼티는 이 문제를 해결하기 위해 &lt;b&gt;읽기 권한은 열어두되, 수정 권한은 닫거나 제어&lt;/b&gt;하는 방식을 사용합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 자동 구현 프로퍼티와 접근 제어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 복잡한 로직이 필요 없을 때, 코드를 한 줄로 매우 간결하게 줄여주는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public float Speed { get; set; }

// 읽기(get)는 누구나, 쓰기(set)는 이 클래스 내부에서만 가능하도록 제한!
public float Speed { get; private set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 백킹 필드(Backing Field)와 로직 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티에 단순히 값을 넣고 빼는 것을 넘어, &lt;b&gt;조건이나 로직&lt;/b&gt;(예: 체력이 0 이하로 내려가지 않게 방어)을 추가해야 할 때가 있습니다. 이때는 실제 데이터가 저장될 진짜 변수인 &lt;b&gt;백킹 필드&lt;/b&gt;를 숨겨두고 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private int _hp; // 실제 데이터가 저장되는 진짜 변수 (백킹 필드)

public int HP
{
    get 
    { 
        return _hp; 
    }
    set 
    { 
        // 외부에서 대입하려는 값은 'value'라는 키워드에 담겨 옵니다.
        if (value &amp;lt; 0) 
            _hp = 0; 
        else 
            _hp = value; 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 식 본문 정의 (=&amp;gt; 화살표)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 전용 프로퍼티를 만들거나 코드를 축약할 때 =&amp;gt; 기호를 사용합니다. 아래의 두 코드는 내부적으로 완전히 동일하게 동작합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 1. 화살표 축약형
public int HP =&amp;gt; _hp;

// 2. 원래 형태
public int HP 
{
    get { return _hp; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.&amp;nbsp; 최신 문법: field 키워드 (C# 13/14)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 set에 제약 조건(ex: value &amp;lt; 0 방지)을 걸기 위해 반드시 _hp 같은 명시적인 백킹 필드를 따로 선언해야 했습니다. 컴파일러가 자동으로 만들어주는 백킹 필드의 이름이 너무 복잡해서 코드에서 직접 부를 수 없었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;C# 13 이후부터는 field 키워드가 도입&lt;/b&gt;되어, 별도의 변수 선언 없이도 숨겨진 백킹 필드에 직접 접근해 데이터를 검증하거나 수정할 수 있게 되었습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public int Score 
{
    get =&amp;gt; field; 
    set 
    {
        if (value &amp;lt; 0) 
            field = 0; 
        else 
            field = value; 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 초기화와 필수 조건 제어 (init, required)&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;b&gt;init (초기화 제어):&lt;/b&gt; 언제든지 값을 바꿀 수 있는 set과 달리, &lt;b&gt;객체를 생성하는 순간에만&lt;/b&gt; 값을 넣을 수 있고 이후에는 수정이 불가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;required (필수 프로퍼티):&lt;/b&gt; 객체를 생성할 때 이 프로퍼티의 값을 &lt;b&gt;반드시 넣도록 강제&lt;/b&gt;합니다. 누락 시 컴파일 에러를 발생시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// 객체 생성 시 Name을 무조건 할당해야 하며, 이후 수정 불가
public required string Name { get; init; }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 계산된 프로퍼티 (Computed Property)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신만의 데이터 저장 공간(백킹 필드, 금고)을 가지지 않고, 누군가 값을 요청(get)할 때마다 내부에 작성된 로직(함수)을 실행하여 즉석에서 결과값을 만들어내는 프로퍼티입니다. (메모리 공간 대신 호출 시점의 CPU 연산을 사용합니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  계산된 프로퍼티를 사용하는 3가지 핵심 이유&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 엇갈림(버그) 원천 차단 (단일 진실 공급원, SSOT)  ️&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터(예: HP) 하나만 남기고, 파생되는 상태(예: IsDead)는 원본을 바탕으로 매번 계산해서 가져옵니다.&lt;/li&gt;
&lt;li&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;/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;외부 스크립트에서는 그저 평범한 변수(player.IsDead)를 읽는 것처럼 아주 쉽게 사용할 수 있습니다. 동시에 내부적으로 어떤 복잡한 계산이나 검증을 거치는지는 완벽하게 숨겨줍니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문은 C# Learn을 바탕으로 공부하고, 제미나이 '학습에 관한 도움 받기'를 통한 공부 후 정리한 내용임을 밝힙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties&quot;&gt;https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/properties&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/properties&lt;/a&gt;&lt;/p&gt;</description>
      <category>개념공부</category>
      <category>C#</category>
      <category>property</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/303</guid>
      <comments>https://febelo0524.tistory.com/303#entry303comment</comments>
      <pubDate>Sat, 14 Mar 2026 01:15:25 +0900</pubDate>
    </item>
    <item>
      <title>동기와 비동기</title>
      <link>https://febelo0524.tistory.com/302</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 문제 상황&lt;/h2&gt;
&lt;pre id=&quot;code_1773295013586&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartNextNode()
{
    if (currentNode == null) return;
    
    if (currentNode is EncounterLineNode || currentNode is CutSceneNode)
    {

        if (currentNode is CutSceneNode) cutSceneUIController.Close(); 
        MoveToNextNode(currentNode.GetNextNode());
    }
    else
    {

        DOVirtual.DelayedCall(0.01f, () =&amp;gt; 
        {
            MoveToNextNode(currentNode.GetNextNode());
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;지난번, 위와 같은 방식으로 버그를 해결한 적이 있습니다. 근본적인 문제는, Model과 View를 분리하고 Model이 먼저 바뀌게 한 다음 View를 업데이트 하는 것이지만, 제출이 30분 남은 상황이었기에 급한대로 처리를 했고, 이후 다음과 같은 피드백을 들었습니다.&lt;/div&gt;
&lt;div&gt;DOTween을 사용하기보다 코루틴을, 그보다 &lt;b&gt;UniTask&lt;/b&gt;라는 서드파티 라이브러리를 사용하는 것이 좋다 !&lt;br /&gt;이 기회에 동기 / 비동기 실행에 대해 공부하고, UniTask 사용법을 익히는 것이 좋다는 생각이 들어 시작합니다.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 동기와 비동기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기(Synchronous) : 앞선 작업이 끝날떄까지 스레드가 멈춰서 대기하는 방식 (Block)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;비동기(Asynchronous) : 작업을 지시해두고 스레드를 블럭하지 않은 채 다른 작업을 하러 가는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773296279524&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Asynchronous programming - C#&quot; data-og-description=&quot;Explore an overview of the C# language support for asynchronous programming by using async, await, Task, and Task.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&quot; data-og-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cm8qy5/dJMb85vNlgS/0lLjLm7w4bgjf7KHA3G62k/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72,https://scrap.kakaocdn.net/dn/KmPos/dJMb89510km/IDuGpYkgVHgRAtpznnmrE1/img.png?width=797&amp;amp;height=460&amp;amp;face=0_0_797_460,https://scrap.kakaocdn.net/dn/zzoQo/dJMb9hCZIFa/1rofCSIzVg9NnKq4kX2zpk/img.png?width=774&amp;amp;height=318&amp;amp;face=0_0_774_318&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cm8qy5/dJMb85vNlgS/0lLjLm7w4bgjf7KHA3G62k/img.png?width=72&amp;amp;height=72&amp;amp;face=0_0_72_72,https://scrap.kakaocdn.net/dn/KmPos/dJMb89510km/IDuGpYkgVHgRAtpznnmrE1/img.png?width=797&amp;amp;height=460&amp;amp;face=0_0_797_460,https://scrap.kakaocdn.net/dn/zzoQo/dJMb9hCZIFa/1rofCSIzVg9NnKq4kX2zpk/img.png?width=774&amp;amp;height=318&amp;amp;face=0_0_774_318');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Asynchronous programming - C#&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore an overview of the C# language support for asynchronous programming by using async, await, Task, and Task.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;공식 문서를 보면 동기 방식에 대한 다음과 같은 코멘트가 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The code blocks the current thread from doing any other work. The code doesn't interrupt the thread while there are running tasks.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이 코드는 현재 스레드가 다른 작업을 수행하지 못하도록 차단합니다. 실행 중인 작업이 있는 동안에는 코드가 스레드를 중단하지 않습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에서 기본적으로 제공하는 비동기 프로그래밍 방법은 Async / Await입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Async / Await를 사용하는 비동기 방식에 대해서는 다음과 같이 설명합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;You can start by updating the code so the thread doesn't block while tasks are running. The&amp;nbsp;await&amp;nbsp;keyword provides a nonblocking way to start a task, then continue execution when the task completes.&lt;br /&gt;&lt;br /&gt;태스크가 실행되는 동안 스레드가 차단되지 않도록 코드를 업데이트하여 시작할 수 있습니다.&amp;nbsp;&lt;br /&gt;await 키워드는 작업을 시작한 다음 태스크가 완료되면 실행을 계속할 수 있는 비블로킹 방법을 제공합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773296629769&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 동기 방식: 1초 동안 메인 스레드가 완전히 멈춤 (게임 멈춤)
Thread.Sleep(1000); 
Debug.Log(&quot;1초 끝!&quot;);

// ⭕ 비동기 방식: 1초를 기다리는 동안 메인 스레드는 다른 게임 로직을 처리함
await Task.Delay(1000); 
Debug.Log(&quot;1초 끝!&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. C# 비동기의 숨겨진 원리 : 상태 기계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 코드는 상태 기계를 사용해 동작합니다.&lt;br /&gt;컴파일러가 Await를 만나면 현재의 실행 상태를 기록하고 제어권을 반환합니다.&lt;br /&gt;이 때 메서드 내부의 지역변수들은 상태 기계의 멤버 변수로 격상되어 메모리에 안정하게 저장되고, 작업이 끝나면 저장된 지점으로부터 코드가 다시 실행되 됩니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773297171575&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;IAsyncStateMachine.MoveNext 메서드 (System.Runtime.CompilerServices)&quot; data-og-description=&quot;상태 시스템을 다음 상태로 이동합니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d1RcQ3/dJMb8Rj0kS1/1bqCh8VqyNefR9TU85uMWK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.runtime.compilerservices.iasyncstatemachine.movenext?view=net-8.0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d1RcQ3/dJMb8Rj0kS1/1bqCh8VqyNefR9TU85uMWK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;IAsyncStateMachine.MoveNext 메서드 (System.Runtime.CompilerServices)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;상태 시스템을 다음 상태로 이동합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 유니티 코루틴과 Async / Await의 차이&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코루틴 : 유니티의 MonoBehaviour 생명주기에 종속되어 있고, 매 프레임마다 엔진이 상태를 체크합니다. 결과를 반환하려면 콜백 등을 써야해&amp;nbsp; 콜백 지옥에 빠지기 쉽습니다.&lt;/li&gt;
&lt;li&gt;Async / Await : 언어 자체의 기능이므로 유니티 생명주기와 무관하게 쓸 수 있고, 일반 동기 함수처럼 return을 사용할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773297451410&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 코루틴 방식 (콜백 필요)
public IEnumerator GetLevelCoroutine(Action&amp;lt;int&amp;gt; onComplete)
{
    yield return new WaitForSeconds(1f);
    onComplete?.Invoke(5); // 결과를 콜백으로 전달
}

// async/await 방식 (직관적인 반환)
public async Task&amp;lt;int&amp;gt; GetLevelAsync()
{
    await Task.Delay(1000);
    return 5; // 일반 함수처럼 바로 리턴 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 Unity에서는 코루틴을 쓰는 것을 처음에 배우게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티 환경에서 기본 Task를 사용하게 되면 다음과 같은 문제점들이 발생하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메인 스레드 보장과 엔진 안전성&lt;br /&gt;&lt;br /&gt;유니티는 내부적으로 C++로 이루어져 있고, 처리 속도를 극한으로 끌어올리기 위해 내부 컴포넌트에 Lock을 걸지 않았습니다.&lt;br /&gt;( 여러 개의 게임 오브젝트의 위치값 등을 수정할 때 , 락이 걸려 있다면 병목 현상이 나타나게 됨 )&amp;nbsp;&lt;br /&gt;이 경우 스레드의 경쟁 상태 문제가 발생할 수 있습니다. ( 여러 곳에서 하나의 컴포넌트에 접근해 값을 바꾸게 되면, 제대로 동작하지 않는 문제 ) . 따라서 Unity는 오직 메인 스레드에서만 유니티 API에 접근해야 한다는 규칙을 정해두었습니다.&amp;nbsp;&lt;br /&gt;기본 Task를 사용할 때, 무거운 연산을 위해 Task.Run 등으로 백그라운드 스레드를 생성하는 경우가 많습니다. 이때 백그라운드 스레드에서 유니티 컴포넌트(UI, Transform 등)를 건드리면 에러가 발생합니다. &lt;br /&gt;&lt;br /&gt;반면 코루틴의 경우,&amp;nbsp; 태생적으로 유니티의 생명주기 안에서만&amp;nbsp; 동작하기 때문에 고민이 필요 없다는 장점이 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;생명주기의 자동 동기화&lt;br /&gt;&lt;br /&gt;Task 기반의 비동기 작업은 유니티와 무관하게 동작하기 때문에 특정 오브젝트가 파괴되었는데도 계속 연산하다 오류를 낼 수 있습니다. 이를 막으려면 매 번&amp;nbsp; CancellationToken을 수동으로 관리해 주어야 합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;반면 코루틴의 경우 MonoBehaviour을 상속받은 오브젝트에 의해서만 실행되기 때문에, 오브젝트가 사라지면 자연스럽게 같이 사라지게 됩니다. (마찬가지로 SetActive(false)에서도 실행이 멈춥니다)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. UniTask&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Async / Await의 직관적인 코드 작성 ( 리턴 값 반환 ) 과 &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Coroutine의 유니티 친화적 장점을 합친다면 더 편하고 좋은 비동기 프로그래밍 방식이 나올 것입니다. 이것이 바로 UniTask입니다. UniTask는 다음과 같은 강점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GC의 감소&lt;br /&gt;&lt;br /&gt;기본 Task는 참조 타입(Class)이므로 생성할 때마다 Heap에 생성되게 되고, 이는 GC를 호출하여 프레임 드랍을 유발합니다.&lt;br /&gt;예를 들어 총알이 생성되고 3초뒤에 사라지는 코드를 Task로 작성하게 된다면 총알이 생성되는 수 만큼의 클래스가 생성되어 Heap 영역에 올라가게 되고, 이는 프레임 드랍을 유발합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;반면 UniTask는 값 타입 (Struct)로 설계되어 Stack 영역에서 생성 및 즉시 소멸되므로 추가적인 메모리 할당이 필요 없습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqh5PS/dJMcaaYNIOg/x1TZiT0WKNdAZkJ1pSXTd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqh5PS/dJMcaaYNIOg/x1TZiT0WKNdAZkJ1pSXTd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqh5PS/dJMcaaYNIOg/x1TZiT0WKNdAZkJ1pSXTd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcqh5PS%2FdJMcaaYNIOg%2Fx1TZiT0WKNdAZkJ1pSXTd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;252&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;메인 스레드로의 안전한 복귀&amp;nbsp;&lt;br /&gt;다음과 같은 코드로 메인 스레드로 이동이 가능합니다&lt;br /&gt;
&lt;pre id=&quot;code_1773298666577&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public async UniTask CalculateAndApplyUIAsync()
{
    // 1. 무거운 연산은 백그라운드 스레드 풀에서 실행 (게임 프레임 방어)
    int result = await Task.Run(() =&amp;gt; ComplexMath.Calculate());

    // 2. 엔진 컴포넌트(UI)에 접근하기 위해 안전하게 메인 스레드로 제어권 복귀
    await UniTask.SwitchToMainThread();
    
    // 3. 메인 스레드 위이므로 UI 조작 시 에러가 발생하지 않음!
    uiText.text = $&quot;결과: {result}&quot;; 
}&lt;/code&gt;&lt;/pre&gt;
기본 Task는 유니티에 최적화되어 있지 않기에 UnitySynchronizationContext라는 과정을 거쳐야 하지만, UniTask는 코어 엔진 루프에 직접 기생하기 때문에 더욱 효율적입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;안전한 작업 취소&lt;br /&gt;&lt;br /&gt;비동기 작업 도중 게임 오브젝트에 변화가 생긴다면, 다음과 같은 코드로 방어할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1773298694812&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 스크립트(오브젝트)가 파괴될 때 비동기 작업도 함께 안전하게 즉시 취소됨
await UniTask.Delay(3000, cancellationToken: this.GetCancellationTokenOnDestroy());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;기본 Task를 사용한다면 다음과 같은 과정을 거쳐야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773299117493&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private CancellationTokenSource cts;

void Start() {
    cts = new CancellationTokenSource(); // 할당 발생
    _ = MyTaskAsync(cts.Token);
}

// 개발자가 실수로 OnDestroy를 안 쓰거나 cts.Cancel()을 빼먹으면 에러 폭탄 발생!
void OnDestroy() {
    if (cts != null) {
        cts.Cancel();
        cts.Dispose();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념공부</category>
      <category>async/await</category>
      <category>coroutine</category>
      <category>UniTask</category>
      <category>동기</category>
      <category>비동기</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/302</guid>
      <comments>https://febelo0524.tistory.com/302#entry302comment</comments>
      <pubDate>Thu, 12 Mar 2026 16:08:58 +0900</pubDate>
    </item>
    <item>
      <title>Delegate,Action,Event,UnityEvent</title>
      <link>https://febelo0524.tistory.com/301</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;ol id=&quot;toc&quot; style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script type=&quot;text/javascript&quot;&gt; $(function(){  $(&quot;#toc&quot;).toc({content: &quot;.tt_article_useless_p_margin&quot;, headings: &quot;h2, h3, h4&quot;}); });&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Action을 사용하면서 다음과 같은 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772629834033&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Action myAction

public void Start()
{
 myAction += () =&amp;gt;{Debug.Log(&quot;Hello&quot;)};
 myAction += PrintHello
}


public void PrintHello()
{
 Debug.Log(&quot;Hello&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 두 방식이 있을 때,&amp;nbsp; 람다식으로 만든 방식은 더 간단할 수 있어도 나중에 구독해제가 되지 않아 문제가 발생한다는 것이다. 그래서 이왕 문제를 발견한 김에 대리자, Action 등의 개념을 다시 잡고자 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 대리자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 변수는 int , string, char 등의 데이터를 담는다. 하지만 '변수'를 담는 것이 필요하다고 생각해 생긴 것이 대리자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Delegate(대리자)는 아래와 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772629983855&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 델리게이트 타입 선언 (이런 형태의 함수만 담을 수 있다는 규칙)
public delegate void MyDelegate(); 

// 2. 담을 함수 준비
void PrintHello() { Debug.Log(&quot;Hello!&quot;); }

// 3. 변수에 함수를 담고 실행
MyDelegate myAction = PrintHello;
myAction(); // &quot;Hello!&quot; 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 익명 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Delegate를 쓰다보니, 한 줄을 실행하기 위해서 매번 새로운 함수를 만들고 연결하는 것이 힘들어 '이름이 없는 일회용 함수'를&lt;br /&gt;사용하는 문법을 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1772631358447&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수 이름 없이 delegate 키워드로 로직을 바로 작성!
MyDelegate myAction = delegate() { 
    Debug.Log(&quot;Hello!&quot;); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 람다식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 delegate를 쓰는 것 마저도 줄여버리기 위해서 람다식을 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772631394035&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// delegate 단어 대신 =&amp;gt; (Goes to) 기호를 사용
MyDelegate myAction = () =&amp;gt; { 
    Debug.Log(&quot;Hello!&quot;); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Action과 Fun&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 public delegate void MyDelegate(); 를 매번 타입에 맞게 선언해줘야 했다. 이를 편하게 하기 위해서&lt;br /&gt;Action ( 반환 타입이 void )와 Func (반환 타입이 존재)를 만들어 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772631502759&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 별도의 delegate 선언 없이 바로 사용 가능
public Action OnPlayerDeath; 

void Start() {
    OnPlayerDeath += () =&amp;gt; Debug.Log(&quot;죽었다!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. event 키워드&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action과 Func를 사용하면 굉장히 편하지만 다음과 같은 문제가 발생한다.&amp;nbsp;&lt;br /&gt;순수 public으로&amp;nbsp; 열어두개 되면 다른 클래스가 내 이벤트를 함부로 덮어씌우거나(=), 맘대로 실행해 버릴 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#의 장점은 캡슐화를 통해서 자신의 값을 스스로 변경시킨다는 것인데, 이를 위반하게 되는 것이다.&lt;br /&gt;따라서 event 키워드를 만들어서 이 문제를 해결했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772632454453&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// event를 붙이는 순간, 외부에서는 오직 += 와 -= 만 가능해진다.
public event Action OnPlayerDeath;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;event 키워드를 사용하면, 외부에서는 구독과 해제만 가능해지므로 안전해진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. UnityEvent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#의 event Action은 성능도 빠르고 좋지만, 유니티 에디터의 인스펙터 창에 보이지 않는다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 유니티 엔진이 C# 델리게이트를 감싸 인스펙터에 노출되도록 만든 것이 UnityEvent이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772632538174&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine.Events;

// 인스펙터 창에서 + 버튼을 눌러 함수를 연결할 수 있음
public UnityEvent OnButtonClicked;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnityEvent는 내부적으로 리플렉션을 사용해 조금 느리긴 하지만 직관적이라는 장점이 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개념공부</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/301</guid>
      <comments>https://febelo0524.tistory.com/301#entry301comment</comments>
      <pubDate>Wed, 4 Mar 2026 22:56:18 +0900</pubDate>
    </item>
    <item>
      <title>9082번 지뢰찾기</title>
      <link>https://febelo0524.tistory.com/300</link>
      <description>&lt;h3 style=&quot;border-left: 10px solid #444; border-bottom: 2px solid #444; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;코딩테스트 : 9082번&amp;nbsp;지뢰찾기&lt;/h3&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h1 style=&quot;color: #585f69;&quot;&gt;&lt;span&gt;지뢰찾기&lt;/span&gt;&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&amp;nbsp;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;시간 제한메모리 제한제출정답맞힌 사람정답 비율
&lt;table id=&quot;problem-info&quot; 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;1 초&lt;/td&gt;
&lt;td&gt;128 MB&lt;/td&gt;
&lt;td&gt;1673&lt;/td&gt;
&lt;td&gt;798&lt;/td&gt;
&lt;td&gt;630&lt;/td&gt;
&lt;td&gt;52.066%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;problem-body&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;지뢰찾기 게임은 2&amp;times;N 배열에 숨겨져 있는 지뢰를 찾는 게임이다. 지뢰 주위에 쓰여 있는 숫자들로 지뢰를 찾을 수 있는데, 한 블록에 쓰여진 숫자는 그 블록 주위에 지뢰가 몇 개 있는지를 나타낸다. 지뢰가 확실히 있는 위치를 *, 숨겨진 블록을 #으로 표시한다. 첫째 줄에는 숫자만 나타나고 둘째 줄에는 *와 #만 나타나는데, 지뢰는 둘째 줄에만 있다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot; style=&quot;background-color: #f5f5f5; color: #333333;&quot;&gt;&lt;code&gt;12110
##*##&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;위의 그림 2&amp;times;5 배열에는 지뢰가 2개가 있다는 것을 알 수 있다. 숨겨진 블록 중 첫 번째 블록에 지뢰가 숨겨져 있고, 나머지 하나는 두 번째 줄의 가운데에 있다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;2&amp;times;N 배열이 주어지면 주어진 배열에 있는 모든 지뢰의 개수(*까지 포함)를 찾는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_input&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;입력의 첫 줄에는 테스트 케이스의 개수 T(1 &amp;le; T &amp;le; 10)가 주어진다. 각 테스트 케이스는 첫 줄에 배열의 크기 N(1 &amp;le; N &amp;le; 100)이 주어지고, 그 다음 두 줄에 걸쳐서 배열이 주어진다. 첫 줄에는 항상 숫자만이 나타나며 이 숫자들 사이에 공백은 없으며, 둘째 줄에 주어지는 입력들 사이에도 공백은 없다. 그리고 이 숫자들은 올바른 값만이 입력으로 들어온다(지뢰의 위치에 대해 불가능한 값은 입력으로 주지 않는다).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_output&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;각 테스트 케이스에 대해서 주어진 배열에 있는 모든 지뢰의 수를 한 줄에 하나씩 출력한다. 지뢰의 수가 여럿이 될 수 있으면 가능한 지뢰의 수 중 최댓값을 출력한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1736319554747&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System.Text;

public class BackJoon
{
    // 그때그떄 초기화 해 주기 
    private static int maxValue = 0;

    public static void Main()
    {
        int T = int.Parse(Console.ReadLine());
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &amp;lt; T; i++)
        {
            int N = int.Parse(Console.ReadLine());
            int[] numbers = new int[N];
            char[] chars = new char[N];

            numbers = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
            chars = Console.ReadLine().ToCharArray();
            sb.AppendLine(FindBomb(numbers, chars).ToString());
        }

        Console.WriteLine(sb.ToString());
    }

    public static int FindBomb(int[] numbers, char[] chars)
    {
        //일단 DFS로 ... 해야 할 것 같은데, 숫자 검사 하고, 가장 왼쪽 가능성부터 Bomb 넣고 넘어가는 식 
        // 3인경우부터 해야 효율적임. 
        for (int i = 0; i &amp;lt; numbers.Length; i++)
        {
            if (numbers[i] == '3')
            {
                for (int idx = i - 1; idx &amp;gt;= i + 1; idx++)
                {
                    chars[idx] = '*';
                }
            }
        }

        maxValue = int.MinValue;
        DFS(numbers, chars,0);
        return maxValue;
    }

    // 주어진채로 다 온다. 
    public static void DFS(int[] numbers, char[] chars, int index)
    {
        // 마지막까지 넘어 갔을 총 갯수 검사
        if (index == chars.Length)
        {
            int count = 0;
            for (int i = 0; i &amp;lt; numbers.Length; i++)
            {
                if (chars[i] == '*') count++;
            }

            if (count &amp;gt; maxValue) maxValue = count;
            return;
        }

        int targetCount = numbers[index];
        // 다 채워졌는지 검사 후 다음 인덱스로 넘어감,
        int currentCount = 0;

        int minIndex = Math.Max(0, index - 1);
        int maxIndex = Math.Min(numbers.Length - 1, index + 1);

        for (int i = minIndex; i &amp;lt;= maxIndex; i++)
        {
            if (chars[i] == '*') currentCount++;
        }

        // 잘못된 경우 
        if (currentCount &amp;gt; targetCount) return;
        // 배치가 맞는 경우 다음 인덱스로 
        if (currentCount == targetCount)
        {
            DFS(numbers, chars, index + 1);
        }
        // 나머지 조건 검사를 할 때 무한 반복이 나지 않게 해 주어야 한다. 
        else 
        {
            for (int i = minIndex; i &amp;lt;= maxIndex; i++)
            {
                if (chars[i] == '#')
                {
                    chars[i] = '*';
                    currentCount++;
                    if (currentCount == targetCount)
                    {
                        DFS(numbers, chars, index + 1);
                        chars[i] = '#';
                    }
                    chars[i] = '#';
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 풀다가 포기한 코드다, DFS를 두 번 사용하여 최적화를 조금 해 보려 하였는데, 능력과 시간의 부족으로 포기하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 원하는대로 그리디 방식으로 풀기 위해 시도하였다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #222222; color: #f7f1ff;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System.Text;

public class BackJoon
{
    // 그때그떄 초기화 해 주기 
    private static int maxValue = 0;

    public static void Main()
    {
        int T = int.Parse(Console.ReadLine());
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &amp;lt; T; i++)
        {
            int N = int.Parse(Console.ReadLine());
            int[]  numbers = new int[N];
            char[] chars = new char[N];

            numbers = Console.ReadLine().ToCharArray().Select(x =&amp;gt; int.Parse(x.ToString())).ToArray();
            chars = Console.ReadLine().ToCharArray();
            
            sb.AppendLine(FindBomb(numbers, chars).ToString());
        }

        Console.WriteLine(sb.ToString());
    }

    public static int FindBomb(int[] numbers, char[] chars)
    {
        //일단 DFS로 ... 해야 할 것 같은데, 숫자 검사 하고, 가장 왼쪽 가능성부터 Bomb 넣고 넘어가는 식 
        // 3인경우부터 해야 효율적임. 
        for (int i = 0; i &amp;lt; numbers.Length; i++)
        {
            if (numbers[i] == 3)
            {
                int minValue = Math.Max(i - 1, 0);
                int maxValue = Math.Min(i + 1, numbers.Length - 1);
                for (int idx = minValue; idx &amp;lt;= maxValue; idx++)
                {
                    chars[idx] = '*';
                }
            }
        }

        maxValue = int.MinValue;
        DFS(numbers, chars, 0);
        return maxValue;
    }


    public static void DFS(int[] numbers, char[] chars, int index)
    {
        // 끝까지 갔을 때 
        if (index == numbers.Length)
        {
            bool isOk = isAllOK(numbers, chars);
            if (isOk)
            {
                int count = 0;
                for (int i = 0; i &amp;lt; numbers.Length; i++)
                {
                    if (chars[i] == '*') count++;
                }

                if (count &amp;gt; maxValue) maxValue = count;
                return;
            }

            return;
        }

        // 들어온 건 그냥 키고 안키고니까, 현재까지 들어온 것이 잘못되었는지 검사해야함.
        // 직전 index의 char가 바뀐 것이므로 두 칸 전의 인덱스가 멀쩡한지 검사해야 한다. 
        if (index &amp;gt;= 2)
        {
            int checkIndex = index - 2;
            bool isIndexOK = IsIndexOK(numbers, chars, checkIndex);
            if (!isIndexOK) return;
        }

        // 현재 위치에서 놓거나 안놓거나
        if (chars[index] == '#')
        {
            chars[index] = '*';
            DFS(numbers, chars, index + 1);
            chars[index] = '#';
        }
        // * 인 경우에는 그대로 
        DFS(numbers, chars, index + 1);
        
    }

    public static bool isAllOK(int[] numbers, char[] chars)
    {
        int len = numbers.Length;

        for (int i = 0; i &amp;lt; len; i++)
        {
            int target = numbers[i];
            int minIndex = Math.Max(i - 1, 0);
            int maxIndex = Math.Min(i + 1, len - 1);

            int count = 0;
            for (int idx = minIndex; idx &amp;lt;= maxIndex; idx++)
            {
                if (chars[idx] == '*') count++;
            }

            if (count != target) return false;
        }

        return true;
    }

    public static bool IsIndexOK(int[] numbers, char[] chars, int index)
    {
        int target = numbers[index];
        int minIndex = Math.Max(index - 1, 0);
        int maxIndex = Math.Min(index + 1, numbers.Length - 1);
        int count = 0;
        for (int idx = minIndex; idx &amp;lt;= maxIndex; idx++)
        {
            if (chars[idx] == '*') count++;
        }

        if (count != target) return false;

        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 풀어놓고 입력을 잘못 받아서.. ( 12110이 당연히 공백이 있는줄 알고 풀었다 ) 왜 안되지만 연발하면서 계속 찾았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 조심할 점이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로는&amp;nbsp; 조건 검사다. 바로 전 인덱스의 #을 *로 바꾸기 때문에, 그 바꾼 인덱스까지 '자신의 주변 지뢰' 라고 인식하는 전전 인덱스가 올바른지 검사해야 했다. 마지막에는 전체를 검사했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로는 DFS를 어떻게 반복할 것인가에 관한 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 다음과 같이 썼다.&lt;/p&gt;
&lt;pre id=&quot;code_1760627285129&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (chars[index] == '#')
{
    // index 위치가 '#'일 때만 탐색을 진행한다
    chars[index] = '*';
    DFS(numbers, chars, index + 1);
    chars[index] = '#';
    DFS(numbers, chars, index + 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#인 경우에만 *로 바꾸거나, # 인 상태로 진행했었다. 그러나 이렇게 되면 char[index]가 *인 경우에&amp;nbsp; DFS를 더 이상 진행하지 않게 되어 버려 답이 나오지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 이렇게 썼다.&lt;/p&gt;
&lt;pre id=&quot;code_1760627437775&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (chars[index] == '#')
{
    chars[index] = '*';
    DFS(numbers, chars, index + 1);
    chars[index] = '#';
    DFS(numbers, chars, index + 1);
}
else 
{
    DFS(numbers, chars, index + 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 똑같은 로직인 chars[index] = '*' 가 두 번 진행된다는 문제가 있어 결국은 다음과 같이 풀었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760627518291&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (chars[index] == '#')
{
    chars[index] = '*';
    DFS(numbers, chars, index + 1);
    
    chars[index] = '#'; 
}

DFS(numbers, chars, index + 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#인 경우에는 두 가지 경로로, *인 경우에는 한 가지 경로로 가게 되었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/백준</category>
      <category>9082번 지뢰찾기</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/300</guid>
      <comments>https://febelo0524.tistory.com/300#entry300comment</comments>
      <pubDate>Fri, 17 Oct 2025 00:12:46 +0900</pubDate>
    </item>
    <item>
      <title>17837번 새로운 게임 2</title>
      <link>https://febelo0524.tistory.com/299</link>
      <description>&lt;h3 style=&quot;border-left: 10px solid #444; border-bottom: 2px solid #444; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;코딩테스트 : 17837번&amp;nbsp;새로운&amp;nbsp;게임&amp;nbsp;2&lt;/h3&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h1 style=&quot;color: #585f69;&quot;&gt;&lt;span&gt;새로운 게임 2&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #5cb85c; color: #ffffff; text-align: center;&quot;&gt;성공&lt;/span&gt;&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&amp;nbsp;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;시간 제한메모리 제한제출정답맞힌 사람정답 비율
&lt;table id=&quot;problem-info&quot; 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;0.5 초&lt;/td&gt;
&lt;td&gt;512 MB&lt;/td&gt;
&lt;td&gt;16960&lt;/td&gt;
&lt;td&gt;8547&lt;/td&gt;
&lt;td&gt;5441&lt;/td&gt;
&lt;td&gt;48.615%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;problem-body&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;재현이는 주변을 살펴보던 중 체스판과 말을 이용해서 새로운 게임을 만들기로 했다. 새로운 게임은 크기가 N&amp;times;N인 체스판에서 진행되고, 사용하는 말의 개수는 K개이다.&amp;nbsp;말은 원판모양이고, 하나의 말 위에 다른 말을 올릴 수 있다. 체스판의 각 칸은 흰색, 빨간색, 파란색 중 하나로 색칠되어있다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;게임은 체스판 위에 말 K개를&amp;nbsp;놓고 시작한다. 말은 1번부터 K번까지 번호가 매겨져 있고,&amp;nbsp;이동 방향도 미리 정해져 있다. 이동 방향은 위, 아래, 왼쪽, 오른쪽 4가지 중 하나이다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;턴 한 번은 1번 말부터 K번 말까지 순서대로 이동시키는 것이다. 한 말이 이동할 때 위에 올려져 있는&amp;nbsp;말도 함께 이동한다. 말의 이동 방향에 있는 칸에 따라서 말의 이동이 다르며&amp;nbsp;아래와 같다. 턴이 진행되던 중에 말이 4개 이상 쌓이는 순간&amp;nbsp;게임이 종료된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;A번 말이&amp;nbsp;이동하려는 칸이
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;흰색인 경우에는 그 칸으로 이동한다. 이동하려는 칸에 말이 이미 있는 경우에는 가장&amp;nbsp;위에 A번 말을 올려놓는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;A번 말의 위에 다른 말이 있는 경우에는&amp;nbsp;A번 말과 위에 있는 모든 말이 이동한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;예를 들어, A, B, C로 쌓여있고, 이동하려는 칸에 D, E가 있는 경우에는 A번 말이 이동한 후에는 D, E, A, B, C가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;빨간색인 경우에는 이동한 후에 A번 말과 그 위에 있는 모든 말의 쌓여있는 순서를 반대로 바꾼다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;A, B, C가 이동하고, 이동하려는 칸에 말이 없는 경우에는&amp;nbsp;C, B, A가 된다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;A, D, F,&amp;nbsp;G가 이동하고, 이동하려는 칸에 말이 E, C, B로 있는 경우에는 E, C, B, G, F, D, A가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;파란색인 경우에는 A번 말의 이동 방향을 반대로 하고 한 칸 이동한다. 방향을 반대로 바꾼 후에 이동하려는 칸이 파란색인 경우에는 이동하지 않고 가만히 있는다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;체스판을 벗어나는 경우에는 파란색과 같은 경우이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 크기가 4&amp;times;4인 체스판 위에 말이 4개&amp;nbsp;있는 경우이다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O25hY/btsRaGiPyJQ/qlP0fYQB83PQsskABlY2uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O25hY/btsRaGiPyJQ/qlP0fYQB83PQsskABlY2uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O25hY/btsRaGiPyJQ/qlP0fYQB83PQsskABlY2uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO25hY%2FbtsRaGiPyJQ%2FqlP0fYQB83PQsskABlY2uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1094&quot; height=&quot;840&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;체스판의 크기와 말의 위치, 이동 방향이 모두 주어졌을 때, 게임이 종료되는 턴의 번호를 구해보자.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_input&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 체스판의 크기 N, 말의 개수 K가 주어진다. 둘째 줄부터 N개의 줄에 체스판의 정보가 주어진다. 체스판의 정보는 정수로 이루어져 있고, 각 정수는 칸의 색을&amp;nbsp;의미한다. 0은 흰색, 1은 빨간색, 2는 파란색이다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;다음 K개의 줄에 말의 정보가 1번 말부터 순서대로 주어진다. 말의 정보는 세 개의 정수로 이루어져 있고, 순서대로 행, 열의 번호, 이동 방향이다. 행과 열의 번호는 1부터 시작하고, 이동 방향은 4보다 작거나 같은 자연수이고 1부터 순서대로&amp;nbsp;&amp;rarr;,&amp;nbsp;&amp;larr;,&amp;nbsp;&amp;uarr;,&amp;nbsp;&amp;darr;의 의미를 갖는다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;같은 칸에 말이 두 개 이상 있는 경우는 입력으로 주어지지 않는다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_output&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;게임이 종료되는 턴의 번호를 출력한다. 그 값이 1,000보다 크거나 절대로 게임이 종료되지&amp;nbsp;않는 경우에는 -1을 출력한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;제한&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_limit&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;4 &amp;le; N &amp;le; 12&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;4 &amp;le; K &amp;le; 10&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1736319554747&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;

public class BackJoon
{
    //문제에서 주어진 순서대로 우좌상하 =&amp;gt; 여기서 문제 dir 풀기 편하게 ( 대칭되게 ) 바꿈
    // 상우하좌하면 안됨... 문제에서 주어졌자나...
    public static int[] dr = new int[4] { 0, 0, -1, 1 };
    public static int[] dc = new int[4] { 1, -1, 0, 0 };
    public static int[,] matrix;
    public static List&amp;lt;Piece&amp;gt; pieces_List = new List&amp;lt;Piece&amp;gt;();
    public static Stack&amp;lt;Piece&amp;gt;[,] pieces;

    public static void Main()
    {
        int[] NK = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
        int N = NK[0];
        int K = NK[1];

        matrix = new int[N, N];
        pieces = new Stack&amp;lt;Piece&amp;gt;[N, N];


        for (int i = 0; i &amp;lt; N; i++)
        {
            int[] row = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
            for (int j = 0; j &amp;lt; N; j++)
            {
                matrix[i, j] = row[j];
                pieces[i, j] = new Stack&amp;lt;Piece&amp;gt;();
            }
        }

        int count = 0;
        for (int i = 0; i &amp;lt; K; i++)
        {
            int[] posDir = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
            int row = posDir[0] - 1;
            int col = posDir[1] - 1;
            int dir = posDir[2] - 1;

            Piece piece = new Piece(count, row, col, dir);
            count++;
            pieces_List.Add(piece);
            pieces[row, col].Push(piece);
        }

        int turn = 0;
        bool isEnd = false;
        while (true)
        {
            turn++;
            if (turn &amp;gt; 1000) break;
            List&amp;lt;Piece&amp;gt; temp = new List&amp;lt;Piece&amp;gt;();
            for (int i = 0; i &amp;lt; K; i++)
            {
                int index = i;
                Piece current = pieces_List[index];

                // 위에 뭐뭐 있는지 체크해야함

                int row = current.row;
                int col = current.col;

                Piece top = pieces[row, col].Pop();
                temp.Add(top);
                while (current != top)
                {
                    top = pieces[row, col].Pop();
                    temp.Insert(0, top);
                }


                int dir = current.dir;

                int nextRow = current.row + dr[dir];
                int nextCol = current.col + dc[dir];

                // 흰 색일 때
                if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; N &amp;amp;&amp;amp; matrix[nextRow, nextCol] == 0)
                {
                    foreach (Piece piece in temp)
                    {
                        piece.row = nextRow;
                        piece.col = nextCol;
                        pieces[nextRow, nextCol].Push(piece);
                    }

                    isEnd = CheckGameEnd(nextRow, nextCol);
                    if (isEnd) break;

                    temp.Clear();
                }
                else if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; N &amp;amp;&amp;amp; matrix[nextRow, nextCol] == 1)
                {
                    for (int j = temp.Count - 1; j &amp;gt;= 0; j--)
                    {
                        Piece piece = temp[j];
                        piece.row = nextRow;
                        piece.col = nextCol;
                        pieces[nextRow, nextCol].Push(piece);
                    }

                    isEnd = CheckGameEnd(nextRow, nextCol);
                    if (isEnd) break;

                    temp.Clear();
                }
                else
                {
                    if (dir == 0) dir = 1;
                    else if (dir == 1) dir = 0;
                    else if (dir == 2) dir = 3;
                    else if (dir == 3) dir = 2;
                    current.dir = dir;
                    nextRow = current.row + dr[dir];
                    nextCol = current.col + dc[dir];
                    // 체스 판을 벗어나거나, 이동했을 때에도 파란색일 때는 
                    if (nextRow &amp;lt; 0 || nextRow &amp;gt;= N || nextCol &amp;lt; 0 || nextCol &amp;gt;= N ||  matrix[nextRow, nextCol] == 2)
                    {
                        // 다시 원래대로 좌표는 바꿔줄 필요 X
                        foreach (Piece piece in temp)
                        {
                            pieces[row, col].Push(piece);
                        }

                        temp.Clear();
                    }
                    else
                    {
                        if (matrix[nextRow, nextCol] == 0)
                        {
                            foreach (Piece piece in temp)
                            {
                                pieces[nextRow, nextCol].Push(piece);
                                piece.row = nextRow;
                                piece.col = nextCol;
                            }
                        }
                        else
                        {
                            for (int j = temp.Count - 1; j &amp;gt;= 0; j--)
                            {
                                Piece piece = temp[j];
                                piece.row = nextRow;
                                piece.col = nextCol;
                                pieces[nextRow, nextCol].Push(piece);
                            }
                        }
                        isEnd = CheckGameEnd(nextRow, nextCol);
                        if (isEnd) break;

                        temp.Clear();
                    }
                }
            }

            if (isEnd) break;
        }

        if (isEnd) Console.WriteLine(turn);
        else Console.WriteLine(-1);
    }

    public static bool CheckGameEnd(int row, int col)
    {
        return pieces[row, col].Count &amp;gt;= 4;
    }

    public class Piece
    {
        public int num;
        public int row;
        public int col;
        public int dir;

        public Piece(int num, int row, int col, int dir)
        {
            this.num = num;
            this.row = row;
            this.col = col;
            this.dir = dir;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직 자체는 짜는데 오래 걸리지 않았으나, 자꾸 답이 이상하게 나왔다. 다음의 두 가지 문제가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 방향 설정 문제&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 방향은 다음과 같이 주어졌다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760515214189&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이동 방향은 4보다 작거나 같은 자연수이고 1부터 순서대로 &amp;rarr;, &amp;larr;, &amp;uarr;, &amp;darr;의 의미를 갖는다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이와 같이 방향을 설정하였으나, 중간에 방향을 바꾸는 코드에서 편의를 위해 + 2 를 하면 반대 방향으로 갈 수 있게 설정했었다. 이로 인해 코드가 달라진 방향을 설정하는 문제가 있어 제대로 된 답이 나오지 않았다.&amp;nbsp; 이는 결국 수동으로 방향을 바꿔주어 해결했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 조건 검사 문제&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #222222; color: #f7f1ff;&quot;&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 체스 판을 벗어나거나, 이동했을 때에도 파란색일 때는 
if (nextRow &amp;lt; 0 || nextRow &amp;gt;= N || nextCol &amp;lt; 0 || nextCol &amp;gt;= N ||  matrix[nextRow, nextCol] == 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 이런 조건 검사가 있었다. '체스 판을 벗어나거나, 이동했을 때에도 파란색일 경우' 를 나타낸 조건이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건을 처음 설정할 때 위에서 다른 조건을 복붙하고 큰 신경을 쓰지 않아 올바른 답이 나오지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #222222; color: #f7f1ff;&quot;&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; N || matrix[nextRow, nextCol] == 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 반대로 조건을 사용해 뒤의 matrix[nextRow,nextCol]에서 잘못된 접근을 해 오류가 났었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 제외한 로직은 문제를 그대로 코드로 옮기려고 노력했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;다른 사람의 흥미로운 풀이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. CheckBlue를 재사용 가능한 함수로 묶는 것이 편하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 방향성 문제는 다음과 같이 변환해서 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 굳이 Stack을 사용할 필요 없다, List로 사용하는 것이 GetRange, RemoveRange, Reverse를 사용할 수 있어 훨씬 편하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760515816483&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  d = r[2] switch
        {
            1 =&amp;gt; 2,
            2 =&amp;gt; 0,
            3 =&amp;gt; 1,
            4 =&amp;gt; 3,
        };&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트/백준</category>
      <category>17837번 새로운 게임 2</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/299</guid>
      <comments>https://febelo0524.tistory.com/299#entry299comment</comments>
      <pubDate>Wed, 15 Oct 2025 17:11:37 +0900</pubDate>
    </item>
    <item>
      <title>17144번 미세먼지 안녕!</title>
      <link>https://febelo0524.tistory.com/298</link>
      <description>&lt;h3 style=&quot;border-left: 10px solid #444; border-bottom: 2px solid #444; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;코딩테스트 : 17144번&amp;nbsp;미세먼지&amp;nbsp;안녕!&lt;/h3&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;미세먼지를 제거하기 위해 구사과는 공기청정기를 설치하려고 한다. 공기청정기의 성능을 테스트하기 위해 구사과는 집을 크기가 R&amp;times;C인 격자판으로 나타냈고, 1&amp;times;1 크기의 칸으로 나눴다. 구사과는 뛰어난 코딩 실력을 이용해 각 칸 (r, c)에 있는 미세먼지의 양을 실시간으로 모니터링하는 시스템을 개발했다. (r, c)는 r행 c열을 의미한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공기청정기는 항상 1번 열에 설치되어 있고, 크기는 두 행을 차지한다. 공기청정기가 설치되어 있지 않은 칸에는 미세먼지가 있고, (r, c)에 있는 미세먼지의 양은 Ar,c이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1초 동안 아래 적힌 일이 순서대로 일어난다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;미세먼지가 확산된다. 확산은 미세먼지가 있는 모든 칸에서 동시에 일어난다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;(r, c)에 있는 미세먼지는 인접한 네 방향으로 확산된다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;인접한 방향에 공기청정기가 있거나, 칸이 없으면 그 방향으로는 확산이 일어나지 않는다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;확산되는 양은 Ar,c/5이고 소수점은 버린다. 즉, &amp;lfloor;Ar,c/5&amp;rfloor;이다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;(r, c)에 남은 미세먼지의 양은 Ar,c&lt;span&gt;&amp;nbsp;&lt;/span&gt;- &amp;lfloor;Ar,c/5&amp;rfloor;&amp;times;(확산된 방향의 개수) 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;공기청정기가 작동한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;공기청정기에서는 바람이 나온다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;위쪽 공기청정기의 바람은 반시계방향으로 순환하고, 아래쪽 공기청정기의 바람은 시계방향으로 순환한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;바람이 불면 미세먼지가 바람의 방향대로 모두 한 칸씩 이동한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;공기청정기에서 부는 바람은 미세먼지가 없는 바람이고, 공기청정기로 들어간 미세먼지는 모두 정화된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;방의 정보가 주어졌을 때, T초가 지난 후 구사과의 방에 남아있는 미세먼지의 양을 구해보자.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6cK3c/btsQ4ON2YSC/P1Kdrhg8jct74QgkicrxYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6cK3c/btsQ4ON2YSC/P1Kdrhg8jct74QgkicrxYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6cK3c/btsQ4ON2YSC/P1Kdrhg8jct74QgkicrxYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6cK3c%2FbtsQ4ON2YSC%2FP1Kdrhg8jct74QgkicrxYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;912&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_input&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 R, C, T (6 &amp;le; R, C &amp;le; 50, 1 &amp;le; T &amp;le; 1,000) 가 주어진다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;둘째 줄부터 R개의 줄에 Ar,c&lt;span&gt;&amp;nbsp;&lt;/span&gt;(-1 &amp;le; Ar,c&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 1,000)가 주어진다. 공기청정기가 설치된 곳은 Ar,c가 -1이고, 나머지 값은 미세먼지의 양이다. -1은 2번 위아래로 붙어져 있고, 가장 윗 행, 아랫 행과 두 칸이상 떨어져 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_output&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 T초가 지난 후 구사과 방에 남아있는 미세먼지의 양을 출력한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1736319554747&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System.ComponentModel.Design;

public class BackJoon
{
    // 상하좌우
    public static int[] dr = new int[4] { -1, 1, 0, 0 };
    public static int[] dc = new int[4] { 0, 0, -1, 1 };
    public static int[,] matrix;
    public static int[,] saveMatrix;
    public static int R;
    public static int C;
    public static int T;
    public static int upStart;
    public static int downStart;
    

    public static void Main()
    {
        int[] RCT = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
        R = RCT[0];
        C = RCT[1];
        T = RCT[2];
        // 각각 Row ,Col, Time

        matrix = new int[R, C];
        for (int i = 0; i &amp;lt; R; i++)
        {
            int[] row = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
            for (int j = 0; j &amp;lt; C; j++)
            {
                matrix[i, j] = row[j];
                if (row[j] == -1) downStart = i;
            }
        }

        upStart = downStart - 1;
        

        // 
        for (int i = 0; i &amp;lt; T; i++)
        {
            // 순회 하면서 확산
            // 항상 모두 0으로 만들어주기
            saveMatrix = new int[R, C];
            for (int row = 0; row &amp;lt; R; row++)
            {
                for (int col = 0; col &amp;lt; C; col++)
                {
                    if (matrix[row, col] != 0)
                    {
                        int amount = matrix[row, col] / 5;
                        for (int dir = 0; dir &amp;lt; 4; dir++)
                        {
                            int nextRow = row + dr[dir];
                            int nextCol = col + dc[dir];
                            if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; R &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; C
                                &amp;amp;&amp;amp; matrix[nextRow, nextCol] != -1)
                            {
                                saveMatrix[nextRow, nextCol] += amount;
                                matrix[row,col] -= amount;
                            }
                        }
                    }
                }
            }

            for (int row = 0; row &amp;lt; R; row++)
            {
                for (int col = 0; col &amp;lt; C; col++)
                {
                    matrix[row,col] += saveMatrix[row, col];
                }
            }
            
            // 공기청정기 작동 로직
            saveMatrix = new int[R, C];
            // 위에 한 칸 밀고 시작. 
            saveMatrix[upStart, 1] = 0; 
            for (int row = 0; row &amp;lt;= upStart; row++)
            {
                for (int col = 0; col &amp;lt; C; col++)
                {
                    // 1은 설정했으니까 2부터 시작 끝까지 !
                    if (row == upStart &amp;amp;&amp;amp; col-1 &amp;gt; 0)
                    {
                        saveMatrix[row, col] += matrix[row, col - 1];
                    }
                    // 끝칸은 이미 설정했으니까 그 다음칸부터 
                    else if (row != upStart &amp;amp;&amp;amp; col == C - 1)
                    {
                        saveMatrix[row, col] += matrix[row + 1, col];
                    }
                    // 마찬가지로 끝칸은 이미 설정했으니까 그 다음칸부터 
                    else if (row == 0 &amp;amp;&amp;amp; col + 1 &amp;lt; C)
                    {
                        saveMatrix[row, col] += matrix[row, col + 1];
                    }
                    // 여기는 시작과 끝칸이 없어야 한다.
                    else if (col == 0 &amp;amp;&amp;amp; row != upStart &amp;amp;&amp;amp; row != 0)
                    {
                        saveMatrix[row, col] += matrix[row - 1, col];
                    }
                }
            }

            // 마찬가지로 우측으로 선회할 때 가장 먼저 한 칸 미뤄줌
            saveMatrix[downStart, 1] = 0;
            for (int row = downStart; row &amp;lt; R; row++)
            {
                for (int col = 0; col &amp;lt; C; col++)
                {
                    // 1은 했으니까 2부터
                    if (row == downStart &amp;amp;&amp;amp; col - 1 &amp;gt; 0)
                    {
                        saveMatrix[row, col] += matrix[row, col - 1];
                    }
                    // 우측 모서리는 빼고 
                    else if (row != downStart &amp;amp;&amp;amp; col == C - 1)
                    {
                        saveMatrix[row, col] += matrix[row -1 , col ];
                    }
                    // 우측 하단 모서리 제외 
                    else if (row == R - 1 &amp;amp;&amp;amp; col != C -1)
                    {
                        saveMatrix[row, col] += matrix[row, col + 1];
                    }
                    // 시작과 끝 빼고 
                    else if (col == 0 &amp;amp;&amp;amp; row != downStart &amp;amp;&amp;amp; row != R -1)
                    {
                        saveMatrix[row, col] += matrix[row +1, col];
                    }
                }
            }

            for (int row = 0; row &amp;lt; R; row++)
            {
                for (int col = 0; col &amp;lt; C; col++)
                {
                    if (row == 0 || col == 0 || row == R - 1 || col == C - 1 || row == upStart || row == downStart)
                    {
                        matrix[row,col] = saveMatrix[row, col];
                    }
                }
            }

            matrix[upStart, 0] = -1;
            matrix[downStart, 0] = -1;
        }

        int count = 0;
        for (int row = 0; row &amp;lt; R; row++)
        {
            for (int col = 0; col &amp;lt; C; col++)
            {
                count += matrix[row, col];
            }
        }

Console.WriteLine(count+2);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재까지 최장길이 코드... 난이도는 엄청 어렵지 않았지만, 뭔가 잘못된 방식으로 풀었는지 풀이가 굉장히 길어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 가지 부분으로 나누어서 풀었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 공기의 순환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 공기청정기의 공기 푸쉬&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번은 금방 풀었지만, 2번이 문제였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외처리를 하나하나 하는 것 밖에는 방법이 떠오르지 않았다. 더 좋은 방법은 밑에서 찾아보기로...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에, -1 당연히처음에만 필요하다고 생각했다.&amp;nbsp; '공기 청정기' 가&amp;nbsp; 작동할때 , -1로 위치 검사를 하지 않고 미리 정해둔 upStart와 downStart로 시작하기에 -1를 무시하고 로직을 짠다면, 나중에 끝나고 더할 때 +2를 해주지 않아도 되니 괜찮지 않을까? 라는 생각이었다. 다만 , 반복에 있어서 -1을 빼 버리면 다음 time의 반복에 공기청정기까지 전파가 되는 문제가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;다른 사람의 흥미로운 풀이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 짧은 코드는 없었다. 다른 분들도 비슷한 방식으로 순환을 했다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/백준</category>
      <category>17144번 미세먼지 안녕!</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/298</guid>
      <comments>https://febelo0524.tistory.com/298#entry298comment</comments>
      <pubDate>Tue, 7 Oct 2025 21:17:45 +0900</pubDate>
    </item>
    <item>
      <title>145000번 테트로미노</title>
      <link>https://febelo0524.tistory.com/297</link>
      <description>&lt;h3 style=&quot;border-left: 10px solid #444; border-bottom: 2px solid #444; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;코딩테스트 :&amp;nbsp; 145000번 테트로미노&amp;nbsp;&lt;/h3&gt;
&lt;div style=&quot;margin: 0.5em 0; padding: 20px; background-color: #cecece; border-radius: 10px; box-shadow: 1px 2px 3px 1px rgba(0,0,0,.1);&quot;&gt;
&lt;div style=&quot;border: 1px solid #a4a4a4; padding: 10px; background-color: #fff; border-radius: 10px;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div id=&quot;problem_description&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;폴리오미노란 크기가 1&amp;times;1인 정사각형을 여러 개 이어서 붙인 도형이며, 다음과 같은 조건을 만족해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;정사각형은 서로 겹치면 안 된다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;도형은 모두 연결되어 있어야 한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;정사각형의 변끼리 연결되어 있어야 한다. 즉, 꼭짓점과 꼭짓점만 맞닿아 있으면 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;정사각형 4개를 이어 붙인 폴리오미노는 테트로미노라고 하며, 다음과 같은 5가지가 있다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPXN4e/btsQ2gd164e/jGQIvE5HLJY9EIA5RzZAW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPXN4e/btsQ2gd164e/jGQIvE5HLJY9EIA5RzZAW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPXN4e/btsQ2gd164e/jGQIvE5HLJY9EIA5RzZAW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPXN4e%2FbtsQ2gd164e%2FjGQIvE5HLJY9EIA5RzZAW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;333&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;아름이는 크기가 N&amp;times;M인 종이 위에 테트로미노 하나를 놓으려고 한다. 종이는 1&amp;times;1 크기의 칸으로 나누어져 있으며, 각각의 칸에는 정수가 하나 쓰여 있다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;테트로미노 하나를 적절히 놓아서 테트로미노가 놓인 칸에 쓰여 있는 수들의 합을 최대로 하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;테트로미노는 반드시 한 정사각형이 정확히 하나의 칸을 포함하도록 놓아야 하며, 회전이나 대칭을 시켜도 된다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_input&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 종이의 세로 크기 N과 가로 크기 M이 주어진다. (4&amp;nbsp;&amp;le; N, M &amp;le; 500)&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;둘째 줄부터 N개의 줄에 종이에 쓰여 있는 수가 주어진다. i번째 줄의 j번째 수는 위에서부터 i번째 칸, 왼쪽에서부터 j번째 칸에 쓰여 있는 수이다. 입력으로 주어지는 수는 1,000을 넘지 않는 자연수이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #585f69;&quot; data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_output&quot;&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 테트로미노가 놓인 칸에 쓰인 수들의 합의 최댓값을 출력한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1759748610466&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BackJoon
{
    private static int N;
    private static int M;
    public static int max = int.MinValue;
    public static int[,] matrix;

    // 상 하 좌 우
    public static int[] dr = new int[4] { -1, 1, 0, 0 };
    public static int[] dc = new int[4] { 0, 0, -1, 1 };
   public static void Main()
   {
       int[] NM = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
       N = NM[0];
       M = NM[1];

       matrix = new int[N, M];
       for (int i = 0; i &amp;lt; N; i++)
       {
           int[] row = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
           for (int j = 0; j &amp;lt; M; j++)
           {
               matrix [i,j] = row[j];
           }
       }

       for (int i = 0; i &amp;lt; N; i++)
       {
           for (int j = 0; j &amp;lt; M; j++)
           {
               bool[,] visited = new bool[N, M];
               DFS((i,j),0,0,visited);
           }
       }

       Console.WriteLine(max);
   }


   public static void DFS((int,int) pos ,int current,int count, bool[,] visited)
   {
       if (count &amp;gt;= 4)
       {
           if (current &amp;gt; max) max = current;
           return;
       }

       for (int i = 0; i &amp;lt; 4; i++)
       {
           int nextRow = pos.Item1 + dr [i];
           int nextCol = pos.Item2 + dc [i];

           if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; M &amp;amp;&amp;amp; !visited[nextRow, nextCol])
           {
               visited [nextRow, nextCol] = true;
               // 다음 카운트를 미리 더해줫음
               DFS ((nextRow,nextCol),current + matrix[nextRow,nextCol], count + 1, visited);
               visited [nextRow, nextCol] = false;
           }
       }
       
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 아래와 같은 조건을 보고 그냥 DFS만으로 풀면 되겠는데 ? 라고 생각했다.&lt;/p&gt;
&lt;pre id=&quot;code_1759748678353&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;폴리오미노란 크기가 1&amp;times;1인 정사각형을 여러 개 이어서 붙인 도형이며, 다음과 같은 조건을 만족해야 한다.

정사각형은 서로 겹치면 안 된다.
도형은 모두 연결되어 있어야 한다.
정사각형의 변끼리 연결되어 있어야 한다. 즉, 꼭짓점과 꼭짓점만 맞닿아 있으면 안 된다.
정사각형 4개를 이어 붙인 폴리오미노는 테트로미노라고 하며, 다음과 같은 5가지가 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개를 이어붙인 것이니까 , 4번 DFS를 반복해 이어 붙인 후 그 칸들이 차지하는 점수를 더해주자 ! 라는 생각으로 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 메모리 초과 ( new visited[,]를 계속 사용해서 ) 가 났고, 그 문제를 고치고 난 후에는 틀리는 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 생각해보니, 다른 모양은 모두 괜찮지만 뫼 산 모양 ( ㅗ ㅏ ㅓ ㅜ ) 모양은 , 현재 칸에서 한 칸씩 더해가는 모양으로는 풀 수 없었다. 하나의 꼭짓점에서 중간 꼭짓점을 들리지 않고는 다른 곳으로 갈 수 없기 때문이다. 따라서 뫼 산 모양만 따로 체크해주는 함수를 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736319554747&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System.ComponentModel.Design;

public class BackJoon
{
    private static int N;
    private static int M;
    public static int max = int.MinValue;
    public static int[,] matrix;
    public static bool[,] visited;

    // 상 하 좌 우
    public static int[] dr = new int[4] { -1, 1, 0, 0 };
    public static int[] dc = new int[4] { 0, 0, -1, 1 };
    
    // row , col 기준으로 4개 좌표 ㅗ ㅜ ㅏ ㅓ 항상 왼쪽부터 위에부터
    private static int[,] mountainRow = new int[4, 4]
        { { 0, -1, 0, 0 }, { 0, 0, 1, 0 }, { -1, 0, 1, 0 }, { 0, -1, 0, 1 } };

    private static int[,] mountainCol = new int[4, 4]
    {
        { -1, 0, 0, 1 }, { -1, 0, 0, 1 }, { 0, 0, 0, 1 }, { -1, 0, 0, 0 }
    };
   public static void Main()
   {
       int[] NM = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
       N = NM[0];
       M = NM[1];

       matrix = new int[N, M];
       visited = new bool[N, M];
       for (int i = 0; i &amp;lt; N; i++)
       {
           int[] row = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
           for (int j = 0; j &amp;lt; M; j++)
           {
               matrix [i,j] = row[j];
           }
       }

       for (int i = 0; i &amp;lt; N; i++)
       {
           for (int j = 0; j &amp;lt; M; j++)
           {
               visited[i, j] = true;
               DFS((i,j),0,0,visited);
               visited[i, j] = false;
               CheckMountain(i,j);
           }
       }
       Console.WriteLine(max);
   }


   public static void DFS((int,int) pos ,int current,int count, bool[,] visited)
   {
       if (count &amp;gt;= 4)
       {
           if (current &amp;gt; max) max = current;
           return;
       }

       for (int i = 0; i &amp;lt; 4; i++)
       {
           int nextRow = pos.Item1 + dr [i];
           int nextCol = pos.Item2 + dc [i];

           if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; M &amp;amp;&amp;amp; !visited[nextRow, nextCol])
           {
               visited [nextRow, nextCol] = true;
               // 다음 카운트를 미리 더해줫음
               DFS ((nextRow,nextCol),current + matrix[nextRow,nextCol], count + 1, visited);
               visited [nextRow, nextCol] = false;
           }
       }
   }

   public static void CheckMountain(int row , int col)
   {
       List&amp;lt;(int,int)&amp;gt; mountain = new List&amp;lt;(int,int)&amp;gt;();

       for (int i = 0; i &amp;lt; 4; i++)
       {

           mountain.Clear();
           for (int j = 0; j &amp;lt; 4; j++)
           {
               int nextRow = row + mountainRow[i, j];
               int nextCol = col + mountainCol[i, j];
               if (nextRow &amp;gt;= 0 &amp;amp;&amp;amp; nextRow &amp;lt; N &amp;amp;&amp;amp; nextCol &amp;gt;= 0 &amp;amp;&amp;amp; nextCol &amp;lt; M)
               {
                   mountain.Add((nextRow,nextCol));
               }
               else break;

               if (j == 3)
               {
                   int maxNum = 0; 
                   foreach ((int, int) mount in mountain)
                   {
                       maxNum += matrix[mount.Item1, mount.Item2];
                   }
                   if (maxNum &amp;gt; max) max = maxNum;
               }
           }
       }

     
   }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트/백준</category>
      <category>145000번 테트로미노</category>
      <author>Cadi</author>
      <guid isPermaLink="true">https://febelo0524.tistory.com/297</guid>
      <comments>https://febelo0524.tistory.com/297#entry297comment</comments>
      <pubDate>Mon, 6 Oct 2025 20:06:55 +0900</pubDate>
    </item>
  </channel>
</rss>