본문 바로가기
UNITY/유니티게임스쿨

[유니티게임스쿨 TIL] C# 기초 문법 이해 :: 파일 입출력, Collection, LINQ

by 램플릿 2024. 6. 4.

 

파일 입출력


파일 쓰기

 

File.WriteAllLines(filepath,lines); // filepath에 lines를 모두 출력한다.

//using System.IO; 네임스페이스 적어주어야 File의 메소드를 쓸 수 있습니다.

static void Main(string[] args)
{
	string filePath = "example.txt";	//파일이 생성될 경로 정의
    
    //파일에 쓰여질 문자열 배열을 정의
    string[] lines = { "First Line", "Second Line", "Third Line" };    
    
    //문자열의 배열을 "example.txt"라는 새 파일에 씁니다.
    //파일이 이미 존재하는 경우 덮어씁니다.
    File.WriteAllLines(filePath, lines);
    
    Console.WriteLine("File written successfully.");
}

 

 

파일의 경로 지정 방법

      파일이 저장될 경로를 지정한다. 상대적 경로 또는 절대적 경로로 작성할 수 있다.

string filePath = "example.txt"; // 상대 경로

//절대 경로를 지정하는 방법
//string filePath = "C:/Users/example.txt";
//string filePath = "C:\\Users\\example.txt";
//string filePath = @"C:\Users\example.txt";

 

 

파일 읽기

  • StreamReader를 사용하기 (한 줄씩 읽기)
//파일이 읽혀질 파일 경로를 정의합니다.
string filePath = "example.txt";

//파일을 읽기 전에 파일이 존재하는지 확인합니다.
if (File.Exists(filePath))
{
	//파일을 읽기 위한 스트림리더
    //using 문은 StreamReader가 올바르게 폐기되도록 합니다.
    //따라서 sr는 해당 스코프를 벗어나면 사용할 수 없습니다. 
    using (StreamReader sr = new StreamReader(filePath))
    {
        //파일을 한줄 씩 Read합니다.
        string line = sr.ReadLine();
        //파일의 끝에 도달할 때까지 각 줄을 읽습니다.
        while (line != null)
        {
            //읽어온 줄을 콘솔에 출력합니다.
            Console.WriteLine(line);

            //그 다음 라인을 읽어옵니다.
            line = sr.ReadLine();
        }
    }
}
else
{	//파일을 찾을 수 없음을 사용자에게 알립니다.
    Console.WriteLine("File not Found");
}
  • 위와 아래 코드는 동일하게 파일을 읽어온 후 출력하는 기능을 한다.

 

  • File.ReadAllLines() 이용하기 (모든 줄 읽어들이기)
//파일이 읽혀질 파일 경로를 정의합니다.
string filePath = "example.txt";

//파일을 읽기 전에 파일이 존재하는지 확인합니다.
if (File.Exists(filePath))
{
    //파일의 모든 줄을 문자열 배열로 읽어들입니다.
    string[] lines = File.ReadAllLines(filePath);
    
    //배열의 각 줄을 순회하며 콘솔에 읽어온 파일을 출력합니다.
    for (int index = 0; index < lines.Length; ++index)
        Console.WriteLine(lines[index]);
    // == foreach (string line in lines) Console.WriteLine(line);
}
else
{
    Console.WriteLine("File not Found");
}

 

 

🎓 새로운 지식 :: 파일에 추가 쓰기 (File.AppendAllText, FileAppendAllLines)
string filePath = "example.txt"; //파일 경로 정의

string newLine = "Fourth line"; //파일에 추가될 새 줄을 정의

//filepath에다가 newLine 데이터를 추가한다. (파일이 존재하지 않으면 생성됨)
File.AppendAllText(filePath, newLine + Environment.NewLine);
	//Environment.NewLine이 \n와 같은 역할을 하기 때문에 줄바꿈이 됨.
	//File.AppendAllText(filePath, newLine)로 작성하면 줄바꿈이 되지 않음.
		
//여러 줄 newLines 데이터를 추가한다.
string[] newLines = { "5th line", "6th line" };
File.AppendAllLines(filePath, newLines);

Console.WriteLine("Line appended Successfully.");

 

 


 

 

C# collection


Array 배열은 데이터가 고정적인 메모리공간에 연속적으로 시퀀스하게 들어간다.

인덱스를 통해 랜덤액세스가 가능하여 시간복잡도가 O(1)인 상수시간이며 굉장히 빠른 접근시간을 가진다.

 

Array에서는 데이터 삽입, 삭제 시 메모리공간을 broken하고 다른 메모리 공간에 다시 순차적으로 정리한다. 즉, heap에서 메모리 재할당이 일어나기 때문에 굉장히 큰 오버헤드가 발생하므로 런타임에 삽입, 삭제가 불가능하며 C#에서는 아예 Insert나 Append가 되지 않는다. 주로 가변하지 않는 자료에 대해 사용한다.

 

C# collection는 자료구조, 자바의 제네릭과 비슷하며 다이아몬드 연산자(꺾쇠 괄호)를 사용한다.

static void ListCollection() //리스트
{            
    List<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };

    numbers.Add(6);
    numbers.RemoveAt(3); //3번째 요소를 삭제합니다.

    foreach (var number in numbers)
        Console.WriteLine(number);
}

static void DictionaryCollection() //딕셔너리 : map, table, 키와 밸류를 갖는다.
{
    Dictionary<string,int> dictionary = new Dictionary<string,int>();
    dictionary["hi"] = 1;

    foreach (var item in dictionary)
        Console.WriteLine(item);
}

 

 

💡질문 :: List<>도 배열처럼 인덱스가 있나요?
⇒ List<>도 배열처럼 인덱스가 존재해 랜덤액세스, 시간복잡도 O(1)의 상수시간 액세스가 가능합니다. 따라서 List<>에도 for 반복문을 통해 각 요소에 접근하는 방법을 사용할 수 있습니다.

 

💡질문 :: 한 리스트에 자료형이 서로 다른 데이터들도 넣을 수 있나요? array에 int도 담고 string도 담으려면 어떻게 하나요?
⇒ List의 타입을 <object>로 하면 가능합니다.

    

+ 강사님 코멘트 : List<object> 또는 ArrayList를 사용하면 여러 유형을 넣을 수 있습니다. ArrayList를 추천하지 않는 이유는 박싱 과정에서의 메모리 부담이 크기 때문입니다.

boxing/unboxing은 주로 다른 언어끼리 데이터를 주고받을 때 일어나는 과정입니다.

이 boxing 과정이 ArrayList가 서로 다른 타입의 데이터를 배열에 가져오는 과정에서도 발생하면서 메모리 부담을 주게 됩니다. 따라서 무작위 타입을 다룰거라면 List<object>를 사용하는 것을 더 추천합니다.

 

 

 


 

 

LINQ (Language Integrated Queary)


LINQ (Language Integrated Queary) [링크] : 데이터를 쿼리로 작성할 수 있다. 즉, 쿼리문을 이용하여 데이터를 조작할 수 있게 해준다.

  • Where : 조건을 만족하는 요소를 필터링
  • Select : 컬렉션의 각 요소를 특정 형식으로 변환.
  • OrderBy, OrderByDescending : 컬렉션을 정렬
  • GroupBy : 컬렉션의 요소를 그룹화
  • Join : 두 컬렉션을 조인
  • Sum, Max, Min, Average : 집계 함수
  • Count : 요소 개수를 계산
💡 질문 :: var는 어떤 자료형인가요?
⇒ C#의 var는 다른 언어의 auto와 비슷하다고 생각하면 쉽습니다. 개발자가 명시적으로 변수의 데이터타입을 정하는 것이 아니라, 할당된 값을 기반으로 데이터타입을 추론하여 컴파일 타임에 자동으로 지정합니다.

 

static void LinqToObject()
{
	//예제 데이터 소스
    List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    //짝수만 골라 필터링
    var evenNumbers = from number in numbers
                      where number % 2 == 0 //number%2 == 0인 요소를 필터링
                      select number;
                      
    //메서드 구문을 사용하여 LINQfunction을 쓰면
    //var evenNumbers = numbers.Where(e => e%2 == 0).Select(e=>e);와 같다.

	//결과 출력
    Console.WriteLine("Even numbers : ");
    foreach (var num in evenNumbers)
    {
        Console.WriteLine(num);
    }
}
  • 위 내용을 LINQ를 사용하지 않고 for문으로 작성하면 아래와 같이 작성할수 있다.
static void LinqToObject()
{
	//예제 데이터 소스
    List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    var oddNumbers = new List<int>();
                
    foreach (var number in numbers)
    {
        if(number % 2 != 0) //짝수가 아닌 수(홀수)만 추가
        	oddNumbers.Add(number);
            
    }
    Console.WriteLine("Odd numbers : ");
    foreach (var number in oddNumbers)
    {
        Console.WriteLine(number);
    }
}

 

  • 복잡한 형식을 사용하는 LINQ
struct Student
{
    //프로퍼티
    public int Id { get; set; } //기본적으로는 zero, 초기화는 생성자에서 하면 된다.
    
    //private int id;
    //public int Id { get{return id;} private set{id=value*1000;} } : 외부에서 set하지 못함.
    
    public string Name { get; set; }
    public int Age { get; set; }
}
void StudentCulling()
{            
    List<Student> students = new List<Student>(){
    	new Student { Id = 1, Name = "Cheolsu", Age = 36 }
        new Student { Id = 2, Name = "Cheolsu2", Age = 18 }
    };
	// 아래 방법으로도 리스트 요소를 추가할 수 있다.
    students.Add(new Student { Id = 3, Name = "Cheolsu3", Age = 45 });
    
    //나이에 따라 학생 정렬
    var sortedStudents = students.OrderBy(student => student.Age).ToList();
    Console.WriteLine("Sorted by Age : "); //출력
    foreach(var student in sortedStudents)
        Console.WriteLine($"Name : {student.Name}, Age : {student.Age}");

    //학생들의 평균 나이 계산
    double averageAge = students.Average(student => student.Age);
    Console.WriteLine($"Average age : {averageAge}");

	//18세 이상인 학생만 추출
    var adultCheolsu = from student in students
                      where student.Age >= 18
                      select student;

    foreach (var student in adultCheolsu)
    {
        Console.WriteLine("id : {0}\n name : {1}\n age : {2}\n", student.Id, student.Name, student.Age);
        //문자열 보간을 사용하여 아래와 같이 작성할 수 있다.
        //Console.WriteLine($"id : {student.Id}, name : {student.Name}, age : {student.Age}");
    }
}

 

🎓새로운 지식 :: 프로퍼티
클래스는 외부에서 직접 멤버변수에 접근하는 것을 제어하기 위해 멤버변수를 은닉하고, getID(), setID()등 특정 메서드를 작성하여 이를 통해 변수에 접근하도록 하는데,
C#에서는 이러한 프로퍼티를 간소화하여 int ID { get; set; } 처럼 사용할 수 있으며 이들 get{ }, set{ }을 접근자라고 한다.
참고 자료👍 :: 문자열 보간
https://woojoolog.tistory.com/4#google_vignette

 

public class Student{ ... }

static void Main(string[] args)
{
	//예제 데이터 소스
    List<Student> students = new List<Student>();
    students.Add(new Student { Id = 1, Name = "Cheolsu", Age = 36 });
    students.Add(new Student { Id = 1, Name = "Cheolsu2", Age = 18 });
    students.Add(new Student { Id = 3, Name = "Cheolsu3", Age = 45 });

	//학생 아이디별 그룹핑
    var result = from student in students
                 group student by student.Id;
    
    // 메서드 구문 LINQ 그룹핑
    // var groups = students.GroupBy(e => e.Id);
                 
	//결과 출력
    foreach (var group in result)
    {
        Console.WriteLine(group.Key);
        foreach (var item in group)
        {
            Console.WriteLine("\t {0}", item.Name);
        }
    }
}