241217

lililllilillll·2024년 12월 17일

개발 일지

목록 보기
23/350

✅ 오늘 한 일


  • C# 교과서 읽기
  • Project Etude


🎮 Project Etude


맵 저장 불러오기

using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// MapEditor.cs에선 List<NoteBlockData>만 사용, MapSaveLoad.cs에서 파일을 저장하고 불러옴
/// </summary>
public class MapSaveLoad
{
    // test
    public void saveMapData(List<NoteBlockData> noteBlockDataList)
    {
        // TODO: noteBlockDataList에 있는 데이터를 easy save로 저장
    }

    public List<NoteBlockData> loadMapData(string mapName)
    {
        List<NoteBlockData> noteBlockDataList = new List<NoteBlockData>();

        // TODO: easy save로 저장된 데이터를 불러와서 noteBlockDataList에 저장

        return noteBlockDataList;
    }
}

일단 여기까지 뼈대만 만들어놓고 테스트해봤는데

통곡의 벽에 가로막혔다.
static해봐도 안되고 뭘 어떻게 해야되는거지
내일 해결해야할듯.

다른 파일에 클래스 정의해놓고 쓰는게 이렇게 어려우면 객체 지향 도대체 어떻게 하는거지



📝 배운 것들


🏷️ C# : 속성(property)

property란
클래스의 필드에 접근하거나 수정하는 방식을 정의하는 메서드이다.

쉽게 비유하면,
변수를 직접 건드리는 건 막아놓고, 문지기를 하나 둬서
해당 변수와의 상호작용은 전부 문지기를 통하게 하는 것이다.

장점
변수에 대한 제어를 통제할 수 있다.
누가 변수를 사용했는지, 어떻게 수정했는지 알 수 있다.
값을 수정하기 전에 유효성 검사를 할 수 있다.

자동 구현 속성

public class Example
{
    public int MyProperty { get; set; } // 자동 구현 프로퍼티
}

이렇게 간략하게 써놓으면

private int _myProperty; // 자동 생성된 백킹 필드

public int MyProperty
{
    get { return _myProperty; }
    set { _myProperty = value; }
}

컴파일러가 알아서 property답게 만들어준다.

하지만 이래서야 그냥 필드를 선언한 것과 차이점이 없어보인다.
public int MyProperty; 이것과 완벽하게 하는 일이 똑같지 않은가?

굳이 필드 대신 자동 구현 속성을 사용하는 이유가 몇 가지 있다.

  • 필드는 인터페이스에서 선언할 수 없다
  • get이나 set 중 필요한 것만 사용할 수 있다.
  • 상속 관계(abstract, virtual, override)를 위한 modifier를 사용할 수 있다.
  • 디버깅할 때 속성에 중단점을 설정하면 누가 언제 접근했는지 알 수 있다.
  • 대부분의 라이브러리는 Data binding에 속성만을 사용할 수 있다.
  • 코드에 일관성을 줄 수 있다. 2개는 필드, 2개는 속성이라면 전부 속성으로 통일하는게 나을 수 있다.

Data binding이란?
앱 UI와 해당 UI가 표시하는 데이터를 연결하는 프로세스이다.
데이터 값이 변경될 때 데이터에 바인딩된 요소에 변경 사항이 자동으로 반영된다.

읽으면 좋을만한 글
https://csharpindepth.com/Articles/PropertiesMatter



📖 C# 교과서


38 속성 사용하기

속성(property)은 필드 값을 읽거나, 쓰거나, 계산하는 방법을 제공하는 클래스 속성을 나타내는 멤버

38.1 속성

프로퍼티: 괄호가 없는 메서드. private 성격이 있는 필드를 public 속성으로 외부에 공개할 때 사용.

public class Car
{
    private string name;
    public string Name { get { return name; } set { name = value; } }
}
Car c1 = new Car();
c1.Name = "Car1";
Console.WriteLine(c1.Name);

38.3 자동으로 구현된 속성

class Car
{
	public string Name { get; set; }
}

이렇게 한 줄로 프로퍼티를 정의하는 걸 auto property라고 함.

38.4 자동 속성 이니셜라이저

public static string Name { get; set; } = "길벗";

속성 선언과 동시에 기본값으로 초기화 가능

38.5 읽기 전용 속성과 쓰기 전용 속성

public string Message { get; private set; } = "읽기 전용 속성";

set에 private을 사용하면 수정이 불가능한 읽기 전용 속성을 만들 수 있다.

38.8 화살표 연산자로 속성과 메서드를 줄여서 표현하기

public int Count
{
	get => count;
    set => count = value;
}

38.9 개체 이니셜라이저

> class Course
. { 
.     public int Id { get; set; }
.     public string Title { get; set; }
. }
> // 1. Initialize with property
> Course csharp = new Course(); csharp.Id = 1; csharp.Title = "C#";
> Console.WriteLine($"{csharp.Id} - {csharp.Title}");
1 - C#
> // 2. Inittialize with object initializer
> Course aspnet = new Course() { Id = 2, Title = "ASP.NET" };
> Console.WriteLine($"{aspnet.Id} - {aspnet.Title}");
2 - ASP.NET

개체 이니셜라이저를 이용하면 좀 더 쉽게 속성을 초기화할 수 있다.

public string Name { get; set; }
public int Age { get; set; }

public Person(string name, int age)
{
	Name = name; Age = age;
}

(...)

Person PC = new Person("백승수", 21);

개체를 생성할 때 생성자로도 속성을 초기화할 수 있다.

38.11 nameof 연산자

class Car
{
	public string Maker { get; set; }
}
> Car car = new Car();
> Console.WriteLine(nameof(car.Maker));
Maker

nameof 연산자를 사용하면 속성이나 메서드 이름을 문자열로 가져올 수 있다.

38.12 익명 형식

> var person = new { Name = "백승수", Age = 21 };
> person.Name
"백승수"
> person.Age
21

익명 형식을 사용하면 특정 클래스로 형식을 만들 필요 없이 간단히 객체를 생성해낼 수 있다.

38.13 익명 형식과 덕 타이핑

"새인데 오리처럼 생겼고, 오리처럼 수영하며, 오리처럼 꽥꽥대면 나는 그 새를 오리라고 하겠다."

익명 형식으로 개체 만든 후에
속성과 형식이 다른 개체를 대입하면 오류 난다.

처음 개체가 만들어지면 그 형식과 동일한 형태로만 다시 할당되는 것을 덕 타이핑이라 한다.

덕 타이핑이란, “내가 정의한 동작을 할 수 있다면 그 타입으로 인정해주는 것이다.” 어렵게 말하면 “객체의 변수, 메소드의 집합이 객체의 타입을 결정하는 것”을 말한다.

사진을 보자. 당장 저것의 이름이 콘센트인지 돼지코인지는 중요하지 않다. 내가 정의한 동작(코드를 꽂을 수 있다!)를 실행할 수 있다면 콘센트로 간주할 수 있다는 것이다.

https://velog.io/@thumb_hyeok/%EB%8D%95-%ED%83%80%EC%9D%B4%ED%95%91%EA%B3%BC-%EA%B5%AC%EC%A1%B0-%EA%B8%B0%EB%B0%98-%ED%83%80%EC%9E%85

38.14 생성자로 속성에 대한 유효성 검사 구현하기

프로그램을 작성하다보면 특정 속성은 반드시 특정 값으로 초기화해야 할 때가 있다.
즉, null 또는 빈 값이 들어오면 안 될 때가 있는데, 이때는 생성자를 사용하여 반드시 특정 값으로 넘겨주도록 강제할 수 있다.

class Car
{
	public string Name { get; private set; }
    public Car(string name)
    {
    	if (string.IsNullOrEmpty(name))
        {
        	// 빈 값이면 강제로 ArgumentException 예외 발생
            throw ne ArgumentException();
        }
        this.Name = name;
    }
}

38.15 메서드로 속성 값 초기화하기

class Fish
{
	public int Weight { get; set; } = 50;
    public void Feed(int weight) => Weight += weight;
    
    class PropertyPractice
    {
    	static void Main()
        {
        	var fish = new Fish();
            fish.Weight = 10;
            fish.Feed(15);
            Console.WriteLine(fish.Weight);
        }
    }
}

38.16 속성에서 ?.와 ?? 연산자를 함께 사용하기


class Person
{
	public string Name { get; set; }
    public Address Address { get; set; }
}

class Adress
{
	public string Street { get; set; } = "알 수 없음";
}

(...)

WriteLine($"person?.Name ?? "아무개"}은(는)" + $"{person?.Address.Street ?? "아무곳"}에 삽니다.");

WriteLine($"첫 번째 사람 : {otherPeople?[0]?.Name ?? "없음"}");

Name 속성이 null이 아니면 해당 Name 속성 사용, 아니면 null 반환하여 ?? 연산자 통해 "아무개" 반환

otherPeople?[0]?.Name은 [0]번째 인덱스의 배열 값이 null인지 확인

39 인덱서와 반복기

39.1 인덱서

public string this[int index]
{
	get { return (index % 2 == 0) ? "짝수" : "홀수"; }
}

인덱서는 클래스의 인스턴스를 배열처럼 사용할 수 있게 하는 구문이다.
쉽게 말해서, 인스턴스 이름 옆에 [index] 붙이면 무슨 행동을 할지 정의하는 메서드이다.

아래의 코드를 보면 조금 더 이해가 잘 된다.

public class Person
{
    public string Name { get; set; }
    public string Gender { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }

    public Object this [int index]
    {
        get
        {
            if (index == 0)
                return Name;
            else if (index == 1)
                return Gender;
            else if (index == 2)
                return Age;
            else if (index == 3)
                return Email;
            else
                return null;
        }
        set
        {
            if (index == 0)
                Name = value.ToString();
            else if (index == 1)
                Gender = value.ToString();
            else if (index == 2)
                Age = Convert.ToInt32(value);
            else if (index == 3)
                Email = value.ToString();
        }
    }

    public override string ToString()
    {
        return "Name: " + Name + " / Gender: " + Gender + " / Age: " + Age + " / Email: " + Email;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person() {
            Name="Tom", 
            Gender="Male", 
            Age= 20, 
            Email="Tom@Test.com" 
        };

        Console.WriteLine("Name: " + person[0]);
        Console.WriteLine("Gender: " + person[1]);
        Console.WriteLine("Age: " + person[2]);
        Console.WriteLine("Email: " + person[3]);
    }
}

코드 출처: https://developer-talk.tistory.com/323

person[0] 넣으면 Name를 반환하고,
person[1] 넣으면 Gender를 반환한다.

이렇게 멤버에 인덱싱을 해줄 수 있는게 바로 인덱서다.
숫자 말고 문자열로 인덱싱을 해줄 수도 있다.

39.2 인덱서를 사용하여 배열 형식의 개체 만들기

public class Car
{
	private string[] names;
    public Car(int length) { names = new string[length]; }
    public string this[int index]
    {
    	get
        {
        	return names[index];
        }
        set
        {
        	names[index] = value;
        }
    }
}

이제 Car의 insatnce인 NewCar을 NewCar[0] 이런 식으로 사용하면 names 멤버를 인덱스대로 사용할 수 있다.

39.4 반복기와 yield 키워드

반복기(iterator)는 배열과 컬렉션 형태의 데이터를 단계별로 실행하는 데 사용할 수 있다.
반복기를 구현할 때는 IEnumerable 인터페이스 (또는 IEnumerable<T> 인터페이스)와 yield 키워드를 사용한다.

class YieldReturn
{
    static IEnumerable MultiData()
    {
        yield return "Hello";
        yield return "World";
        yield return "C#";
    }

    static void Main()
    {
        foreach (var item in MultiData())
        {
            Console.WriteLine(item);
        }
    }
}

실행하면

Hello
World
C#

요청이 올 때마다 하나씩만 반환된다는 걸 알 수 있다.

IEnumerable<T>로 컬렉션 형태의 데이터 반환받기

> using System.Collections.Generic;
> 
> // 1. not using yield
> static IEnumerable<int> Greater1(int[] numbers, int greater)
. {
.     List<int> temp = new List<int>();
.     foreach (var n in numbers)
.     {
.         if (n > greater)
.         {
.             temp.Add(n);
.         }
.     }
.     return temp;
. }
> // 2. using yield
> static IEnumerable<int> Greater2(int[] numbers, int greater)
. { 
.     foreach(var n in numbers)
.     {
.         if (n > greater)
.         {
.             yield return n;
.         }
.     }
. }
>
> int[] numbers = { 1, 2, 3, 4, 5 };
>
> foreach (var n in Greater1(numbers, 3))
. {
.     Console.WriteLine(n);
. }
4
5
> foreach(var n in Greater2(numbers, 3))
. {
.     Console.WriteLine(n);
. }
4
5

yield return으로 반복해서 처리해야 하는 데이터를 반환할 때 유용하게 사용할 수 있다.

IEnumerable<T>의 MoveNext() 메서드와 Current 속성

IEnumerable<T> 형태는 내부적으로 MoveNext() 메서드와 Current 속성을 사용한다.

> IEnumerable<int> GetNumbers()
. {
.     yield return 1;
.     yield return 2;
.     yield return 5;
. }
> 
> var nums = GetNumbers().GetEnumerator();
> nums
GetNumbers { 1, 2, 5 }
> 
> nums.MoveNext();
> nums.Current
1
> nums.MoveNext()
true
> nums.Current
2

다음 값 이동, 반환하는 예제



profile
너 정말 **핵심**을 찔렀어

0개의 댓글