1.형
행은 변수를 어떻게 처리할지 결정하는 역할을 한다.
int = 1 ; // int 변수 x에 값 1을 대입한다
char c = c; // char형 변수 c에 값 C를 대입한다
// char n = 1 // char형 변수 n에 값 1을 대입하면 컴파일 오류가 발생한다
형은 숫자와 문자뿐만 아니라 true/false 같은 bool형을 포함하여 여러가지 종류가 있다.
또한, 숫자형에도 여러가지 종류가 있다.
bool x = true;
byte b = 255;
int i = 1;
float f = 1.0f;
double d = 1.0;
2.행 추론
형 추론이란 형을 선언하지 않아도 컴파일러가 값을 추론하여
자동으로 형을 할당하는 기능, 형 추론은 메서드 안에서만 가능하다.
int a1 = 1; // int형 변수 a1
var a2 = 1; // int형 변수 a2
var를 사용할 때 초깃값을 대입하지 않으면 컴파일 오류가 발생한다.
// var b; //var는 초깃값을 넣지 않으면 컴파일 시 오류가 발생한다
var를 사용하면 형 추론으로 어떤 형이든 적용시켜주므로
아무리 형 이름이 길어도 항상 세 문자로 표편할 수 있다.
메서드 안에서 모든 형을 세 문자로 표현할 수 있어 코드 가독성이 높아진다
// 좌변이 모두 세 문자로 표현되므로 소스가 깔금하다
var a = 1;
var s = "aaa";
var dic = new Dictionary<string, string>();
Object형처럼 어떤 형으로도 대체할 수 없는 형도 있긴 하지만,
초깃값을 할당하지 않으면 사용 할 수 없는 형 추론과는 다른 얘기다.
값을 판별해서 형을 할당하려면 형 추론을 사용해야 한다.
3.연산자
연산자란 덧셈(+)이나 뺄셈(-)과 같은 연산을 하는 기호이다.
연산의 대상이 되는 것을 오퍼랜드라고 한다.
오퍼랜드와 연산자로 구성된 것을 식이라고 부른다.
연산자는 계산의 목적 외에도 다양한 종류가 있다.
연산자는 왼쪽에서 오른족의 순서로 처리된다(단, 대입은 오른쪽에서 왼쪽).
그러나 우선순위가 높은 연산자가 있느면 먼저 처리한다. 곱셈(*)이 덧셈(+)보다
우선순위가 높으므로 1 + 2 * 3 식은 처음에 *이 계산된 다음 +가 계산된다.
// 연산자의 우선순위 순서대로 계산된다
var x = 1 + 2 * 3;
var x = 1 + 6;
var x = 9;
(1 + 2) * 3과 같이 괄호가 있느면 괄호를 먼저 계산한다.
// 괄호가 있으면 그 부분이 먼저 계산된다.
var x = (1 + 2) * 3;
var x = 3 * 3;
var x = 9;
연산자 | 식 | 용도 | 구분 |
% | x % y | 나머지 | |
+ | x + y | 덧셈 | 기법 |
– | x – y | 뺄셈 | |
<< | x << y | 비트를 왼쪽으로 시프트 | 시프트 |
>> | x >> y | 비트를 오른쪽으로 시프트 | |
< | x < y | x가 y보다 작으면 true | 관계 |
> | x > y | x가 y보다 크면 true | |
<= | x <= y | x가 y보다 작거나 같으면 true | |
>= | x >= y | x가 y보다 크거나 같으면 true | |
== | x == y | x와 y가 같으면 true | 비교 |
!= | x != y | x와 y가 다르면 true | |
& | x & y | x와 y의 각 비트가 1이면 1(예:10 & 11 = 10) | 논리 AND |
^ | x ^ y | x와 y의 각 비트가 다르면(예:10 ^ 11 =01) | 논리 XOR |
| | x | y | x와 y의 각 비트가 1이면(예:10 | 11 = 11) | 논리 OR |
&& | x && y | &와 같다. 그러나x 가 false면 y는 평가되지 않는다 | 조건 AND |
|| | x || y | |와 같다. 그러나 x가 true면 y는 평가되지 않는다. | 조건 OR |
?? | x ?? y | x가 null이면 y,x가 false이면 x | null 합계 |
?: | x ?y:z | x가 true이면y,x가 false이면 z | 조건 |
= | x = y | 대입하고 1 증가, 대입하고 1감소 | 대입 |
+= | x+= y | x = x + y와 같다(-=,/=,%=,&=,|=,^=도 모든같인 방식) | |
=> | ()=>{} | 람다 식 |
※비트란 2진수에서 숫자 한 자리를 말함
4.문
문(Statement)이란 프로그램 작성하는 처리의 한 단위다.
식만으로는 프로그램을 작성할 수 없고 반드시 문 형태로 작성해야 한다.
문이라 부를 수 있는 것에는 두 가지가 있다. 그중 하나로 ;(세미클론)
으로 끝나는 코드 한 행을 문이라 한다.
int a; // 선언문
a = 1; // 식으로 된 문(a = 1은 대입식)
문에 해당하는 다른 한가지는 {}(중괄호)로 묶은 려러 행의 코드다.
중괄호로 묶인 부분을 블록이라고 부른다.
변수는 블록 안에 유효하고 변수의 이러한 유효 범위를 스코프라고 한다.
int a = 0;
if(a == 1) // 선택문(a == 1은 비교식)
{
int b;
b = 2; // 식으로 된 문(b = 2는 대입식)
}
// b = 3; // x: 스코프 밖에서 접근하려고 하면 컴파일 오류가 발생한다.
문에는 선택문, 반복문과 같은 다야한 종류가 있으면 각각에 관련된 키워드가 있다.
5.메서드
메서드란 프로그램 안에서 이루어지는 처리에 별도의 이름을 붙이고 다른 위치에서
호출되도록 만든 것이다. 메서드에는 인수와 반환값을 지정할 수 있다.
인수는 몇 개라도 넘겨줄 수 있으며 반환값은 return과 함께 쓴 값으로 이 값이 반환된다.
void Walk() {} // 인수 없음, 반환값 없음
int Stop(int x) {return x} // 인수 한 개, 반환값 있음
void Run(int x, int y); // 인수 두 개, 반환값 없음
메서드를 호출하려면 파라미터의 개수에 맞춰 인수를 정의해야한다.
반환값을 있을 떄는 반환값의 현에 맞춰 값을 대입한다.
Walk(); // 인수 없음, 반환값 없음
var y = Stop(1); // 인수 한 개, 반환값 있음
Run(10, 20); // 인수 한 개, 반환값 없음
6. 배열
배열이란 형이 같은 변수 여러 개를 한꺼번에 처리하기 위한 것이다.
반복 처리와 함께 사용하면 같은 처리를 여러 번 작성할 필요가 없어져
프로그램을 수정하기도 쉽다.
작성 방법은 아래 코드와 같이 new라고 쓴 다음에 형과 같이를 지정하여
실체를 만들어준다.
var faces = new string[3]; // string을 세 개 넣을 수 있는 배열 faces
배열에 값을 넣을 때는 변수 이름에 인덱스를 붙여서 한 개씩 처리한다.
인덱스는 첨자라고도 부르며 1이 아니라 0부터 시작한다는 것에 주의하자.
faces[0] = "anngry";
faces[1] = "conf";
faces[2] = "smile";
정의할 때 값을 넣는 방법도 있다. 아래코드에 적힌 두 가지 방법은 같은 내용이다.
string[] faces = {"angry", "sap", "smile"};
var faces = new[] {"angry", "sap", "smile"};
그리고 2차원 배열, 3차원 배열이나 배열의 배열을 만들 수도 있다.
다치원 배열은 행렬을 다룰 때 편리하지만, 각 요소를 파악하기 어렵다는 단점이
있으므로 주의해서 사용해야한다.
string[,] faces2;
string[,,] faces3;
string[][] faces4;
7. 예외 처리
예외 처리란 프로그램에서 예측하지 못한 상황에 대처하는 것을 말한다.
예를 들어 파일 접속 프로그램을 만들어서 실행했는데 어떠한 이유로 파일이
존재하지 않는다면 예외가 발생한다. 작성법은 아래 코드와 같이 try-catch
라고 쓰면 된다.
try{ // 예외가 발생할 수 있는 부분을 try 블록으로 감싼다.
// 아래 행에서 파일에 접근하지 못하면 예외가 발생한다.
var test = File.ReadAllTest(@"C:\log.txt");
}catch(IOException e){ // 발생한 예외를 잡아서 catch 블록에서 처리한다.
Debug.Log("파일에 접근할 수 없습니다.");
}
또한, finally 블록도 있다.finally는 어떤 상황에서도 반드시 호출되므로
리소스 해제와 같이 반드시 처리해야 하는 부분에 사용한다. try-catch-finally
혹은 try-finally라고 쓴다.
WWW www =null;
try {
www = new WWW("https://www.yahoo.co.jp/");
}finally{ // 예외 발생 여부와 상관없이 finally 블록은 항상 실행된다.
if (www != null) {
www.Dispose();
}
}
만약 리소스를 해제 하는데 Dispose를 사용한다면 try-finally보다
편리한 작성법이 있다. 바로using 문이다. using 문의 작성법은 아래
코드와 같다, 앞서 코드에서 했던 처리과 같다.
using (WWW www= new WWW("https://www.yahoo.co.jp/"))
{
}
8. 정리
9. 클래스 기초
클래스의 기초내용
9.1 클래스
클래스나 구조체 안에는 다양한 형과 메서드가 있으며 자신이 선언한 이름을
사용하여 이들을 다룰 수 있다.
class Person
{
public string name; // 멤버 변수
public int age; // 멤버 변수
}
자신이 직접 만든 클래스는 아래 코드 처럼 사용할 수 있으며 이때 new로 생성한
클래스의 실체를 인스턴스라고 한다. 멤버 변수는 인스턴스에 점(.)을 붙여서 사용한다.
Person p = new Person(); //Person 클래스에 포함된 p 인스턴스 생성
p.name = "UnityChan"; //변수에 값을 대입하려면 인스턴스에 점으 붙이고 변수를 쓴다
p.age = 17;
클래스 안에서는 인스터스를 생략해도 멤버 변수를 사용할 수있지만,
인스턴스를 명시적으로 쓰고 싶은 때는 아래 코드와 같이 this를 사용한다.
class Person
{
//this를 붙이지 않아도 된다.
//단, 다른 클래스에 같은 이름의 변수가 있으면 this를 붙여야 한다.
public void SetName(String name) { this.name = name;}
}
9.2 멤버
클래스에는 다양한 기능의 멤버가 있다.
각 멤버에 관한 설명는 아래 표에 정리해두었다.
멤버 | 설명 | 작성법 |
생성자 | 클래스를 초기화 할 때 실행된다. | Test(){···} |
소멸자 | 클래스를 해제할 때 실행된다. | ~Test(){···} |
상수 | 말 그대로 상수 | const string Fix=”Fix”; |
필드 | 말 그대로 필드 | int field; |
메서드 | 말 그대로 메서드 | void Method(){···} |
프로퍼티 | 클래스 밖에서는 변수처럼, 클래스 안에서는 메서드처럼 사용할 수 있다. | int Property{get;set;} |
이벤트 | 프로퍼티처럼 사용할 수 있는 메서드 | event EventHandler Event; |
인덱서 | 프로퍼티처럼 사용할 수 있는 배열 | int this(int i){···} |
연산자 | 오버로드된 연산자 | static Test operator+(Test x, Test y) {···} |
중첩된 형 | 클래스 내에 선언된 클래스 등 | static Test operator+(Test x, Test y) {···} |
9.3 접근 제한자
접근 제한자란 멤버 변수에 접근 방법을 제어하기 위한 것이다.
자세한 내용은 표 2를 참고, 클래스 내부에서만 접근할 수 있는
private필드와 클래스 외부에서 접근할 수 있는 public메서드
를 준비한다. 이렇게 하는 것은 접근 제한자를 사용하는 방법 중 한 가지
class Person
{
// Private로 지정된 name 필드는 이 클래스 안에서만 수정 가능하다.
private string name;
// pubilc으로 지정된 Set 메서드는 이 클래스 밖에서도 접근할 수 있다.
public void SetName(string name) { this.name = name; }
// public으로 지정된 Get 메서드는 이 클래스 밖에서도 접근 할 수 있다.
public string GetName() { return; }
}
접근 제한자 | 설명 |
public | 자유롭게 접근할 수 있다. |
protected | 해당 클래스와 그 클래스를 상속한 클래스에서만 접근 할 수 있다. |
internal | 해당 어셈블리어에서만 접근할 수 있다. |
protected internal | 해당 어셈블리나 그 클래스를 상속한 클래스에서만 접근할 수 있다. |
internal protected | 해당 어셈블리나 그 클래스를 상속한 크래스에서만 접근할 수 있다. internal protected를 사용해도 된다 |
private | 해당 클래스 내부에서만 접근할 수 있다, |
// 접근 제한자는 클래스 내부에서만 접근할 수 있다.
public class Test
{
// 접근 제한자는 어떤 멤버 이름 앞에도 붙일 수 있다.
public Test() { }
public const string Fix = "fix";
public int fieId;
protected void Method() { }
public int Property { get; set; }
public event EventHandIer Event;
public int this[int i] { ··· }
internal static static Test operator +(Test x, Test y) { ··· }
protected internal class Test2 { }
}
9.4 프로퍼티와 인덱서
프로피터란 클래스 밖에서는 변수로,클래스 안에서는 메서드처럼 취급되는 것을 뜻한다.
앞서 접근 제한자 부분에서 살펴본 캡슐화르 기억하는가? 캡슐화는 유용하지만.
필드 하나에 메서드 두개가 필요하다. 아래코드와 같이 작성하면 캡슐화를 사용한 코드와 의미가 같다.
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
만약 get과 set 둘 다 아무 처리도 하지 않는다면 더 간단하게 작성할 수 있다.
아래 코드는 위 코드과 의미가 같다. 이 기능을 자동 프로퍼터라 한다.
public string Name { get; set; }
또한, 프로퍼터의 get이나 set에도 접근 제한자를 붙일 수 있다.
예를 들어 외부에서 읽을 수는 있지만, 수정은 할 수 없는 프로퍼티를 만들 수 있다.
public string Name { get; private set; }
그리고 인덱서를 사용하면 배열로 프로퍼티와 같은 효과를 낼 수 있다.
프로퍼티 이름 대신 this[]를 사용하는 점과 첨자를 사용한다는 점 말고는
프로퍼티와 다르지 않다.
private int[] x;
public int this[int I]
{
get { return x[i]; }
set { x[i] = value; }
}
유니티에서 개발할 때 주의할 사항이 있다. 프로퍼티는 public으로 지정되어도
Inspector에서는 사용할 수 없다는 것이다. Inspector에서 값을 변경하려면
아래코드와 같이 작성해야 한다.
[SerializedField]
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
9.5 생성자
생성자는 초기화를 위한 메서드이며 인스턴스를 생성할 때 반드시 호출된다.
반환값은 없으며 인수는 어떤 것이라도 가능하다.생성자 사용법은 아래코드와
같다. 생성자의 이름은 해당 클래스와 같아야 한다.
class Person
{
public Person(string name) //생성자
{
this.Name = name;
}
public string Name { get; set; }
}
생성자를 포함한 클래스를 사용할 때 는 아래코드과 같이 호출한다.
Person p = new Person("UnityChan");
9.6 static
클래스를 사용하다보면 ‘공용 변수와 메서드가 있었으면 편리할 텐데···”할 때가
종종 있다. 이럴때는 static으로 정적 멤버를 만들면 된다.정적 멤버란 인스턴스 없이
클래스에서 직접 호출할수 있는 멤버 직접 호출할 수 있는 멤버를 의미한다.
public class Person
{
public static int count; // static을 붙여서 선언
}
Person.count = 1; // 클래스 이름을 사용하여 변수에 직접 접근한다.
정적 멤버중에 static이 없는 멤버를 인스턴스 멤버라고 한다.
단,정적 멤버는 인스턴스 멤버에 접근할 수 없으므로 주의하자.
public class Person
{
public static Name { get; set; }
private static int count;
public static void Countup()
{
count++; // 맞음: 정적 메서드는정적 멤버에 접근할 수 있다.
// Name = "UnityChan" // 틀림: 인스턴스 멤버로 접근하면 컴파일 오류가 발생한다.
}
}
9.7 상속
상속은 이미 만들어진 클래스의 기능을 바탕으로 새로운 클래스를 만들어서 새 멤버
를 추가할 수 있는 가능이다.
여기서 바탕이 되는 클래스를 부모 클래스(기반 클래스),새롭게 만든 클래스를 자식
클래스(파생 클래스)라고 한다. 자식 클래스를 만들 때,는 클래스 이름 뒤에:클론을
붙이고 그 뒤에 부모 클래스 이름을 쓴다.
public class Person // 부모 클래스 Person
{
public string Name {get; set; }
}
public class Girl : Person // 자식 클래스 Girl
{
public string Weapon {get; set; } // 자식 클래스에만 멤버도 선언할 수 있다.
}
자식 클래스 Girl은 부모 클래스 Person의 기능 중 private로 지정된 것을 제외하고
모두 사용할수 있다.실제로 자식 클래스에는 없는 멤버인Name에 접근해도 오류가 발생
하지 않는다.
Girl p = new Girl();
p.Name = "UnityChan"; // 부모 클래스에서 선언된 Name은 자식 클래스에서
// 다시 선언하지 않아도 그대로 사용할 수 있다.
또한,자식 클래스의 인스턴스는 부모 클래스의 변수에 할당 할 수 있다.
Person p = new Girl(); //맞음:자식 클래스는 부모 클래스의 것을 가지고 있으므로 성립
// Girl p = new Person(); //틀림:부모 클래스는 자식 클래스의 것을 가지고 있지 않으므로
9.8 추상과 인터페이스
추상 클래스란 그 클래스 자체로는 인스턴스를 생성하지 못하고 자식 클래스로만
인스턴스를 생성할 수 있는 클래스다. 아래코드와 같이 작성하며 해당 클래스 앞에
sbstract를 붙인다.
abstract public class Person { } //추상 클래스 Person
public class Girl : Person { } //추상 클래스의 자식 클래스 Girl
// Person p1 = new Person(); //틀림: 추상 클래스는 인스턴스를 생성할 수 없다.
Girl p2 = new Girl(); //맞음: 추상 클래스를 상속한 클래스로는 인스턴스를
//생성할 수 있다.
인터페이스의 특징은 한 클래스에서 여러 개의 인터페이스를 상속할 수 있다는 것이다.
// 인터페이스는 여러 개를 상속할 수 있다.
public class Girl : IPerson, IEnumerable, IList { ··· }
9.9 정리
10 C# 한 걸음 더
이번 에는 C#의 기본적인 사항에 관해 조금 설명하겠다.
10.1 값 형식과 참조 형식
C#의 형에는 값 형식(Value Type)과 참조 형식(Reference Type)이 있다.
값 형식에는 struct 기반인 char와 int가 있고,참조 형식에는Class 기반인
string과 배열이 있다. 자세한 내용은 아래 표를 참고
public struct XStruct { public int x; } //값 형식 구조체
public class XClass { public int x; } //참조 형식 구조체
구체적인 예를 통해 두 형식의 차이점을 살펴보겠다.
값 형식과 참조 형식을 각각 메서드의 인수로 넘겨주어 메서드 안에서 직접 값을 대입
하여 확인해보자. 값 형식은 값의 복사본을 전달하므로 변경 사항이 남지 않지만. 참조
형식은 값의 위치,즉 어느 곳을 참조해야 하는지를 복사 하므로 변경 사항이 남지 않지만.
참조 형식은 값의 위치,즉 어느 곳을 참조해야 하는지를 복사하므로 변경 사항이 남는다.
void Test ()
{
XStruct s = new XStruct();
XClass c = new XClass();
// 호출 전 상태.구조체 s의 x는 0, 클래스 cdml x는 0
XChange(s, c);
// 호출 후 상태. 구조체 s의 x는 0, 클래스 c의 x는 1
}
void XChange(XStruct s1, XClass c1) //구조체와 클래스를 인수로 넘겨주어 멤버의 값을 수정한다
{
s1.x = 1; //구조체의 멤버 x에 1을 대입
c1.x = 1; //클래스의 멤버 x에 1을 대입
}
11.1 네임스페이스
네임스페이스(namespace)[이름 공간]란 마치 윈도에서 파일을 폴더로 분류하는 것과
비슷한 개념이다. 폴더가 다르면 그 안에 파일이나 폴더의 이름이 같아도 괜찮은 것처럼
C#에서도 네임스페이스가 다르면 같은 이름을 붙일 수 있다. 네임스페이스를 작성할 때는
namecspace 지시사에 이름을 붙여 {}(중괄호)를 묶으면 된다.줄괄호로 둘려싸인 부분이
네임스페이스의 이름이다.
namespace Ootori
{
//Ootori로 둘러싸인 부분이 전부 Ootori 네임스페이스다.
public class Kohaku.Korokke();
}
네임스페이스 이름 뒤에 클래스 이름을 입려하면 네임스페이스를 사용할 수 있다.
Ootori.Kohaku.Korokke();
using 지시자를 사용하면 네임스페이스에 정의한 이름을 간편하게 다룰 수 있다.
코드 시작부분에 using을 선언함으로써 네임스페이스의 이름을 생략하고 사용할 수 있다.
using Ootori; //코드 시작 부분에 using으로 네임스페이스를 선언한다
Kohaku.Korokke(); //사용할 때는 네임스페이스를 생략할 수 있다.
Ootori.Kohaku.Korokke(); //using으로 선언했어도 using을 선언하지 않았을 때처럼 표현할 수 있다
12.1 형변환
형변환(cast)이란 어떤 형의 변수를 다른 형식으로 변환하는 것이다. 형은 한 번 선언
하면 나중에 다른 셯의 값을 대입할 수 없다. 그러나 대입해도 값이 변하지 않을 때와
형을 형 변환했을 때는 값을 대입할 수 있다. 형변환을 사용할 때는 변수 앞에 괄호로
대입하려는 형을 지정하면 된다.
int a = 123; double x;
x = a; //값이 변하지 않으므로 int형을 double형에 대입할 수 있다.(x = 123.0)
a = (int)x; //double형의 변수를 int로 형변환하면 대입할 수 있다.(a = 123)
// a = x; //int형 변수 a에 double형 값을 대입하면 컴파일 오류가 발생한다.
클래스도 마찬가지다. 부모 클래스를 자식 클래스로 변환하려면 형변환을 해야 한다.
Person p1 = new Person();
UnityChan p2 = new UnityChan();
p1 = p2; //자식 클래스의 참조를 부모 클래스에 대입하는 것은 괜찮다.
// p2 = p1; //부모 클래스의 참조르 자식 클래스에 대입하면 컴파일 오류가 난다.
p2 = (UnityChan)p1 //부모 클래스의 참조를 형변환해서 자식 클래스에 대입하는 것은 괜찮다
참조 형식은 as 연산자를 사용해서도 형변환을 할 수 있다.
이 두 방법의 차이는 형변환에 실패했을 때 드러난다.
as를 사용해서 형변환한 것은unll을 반환하고 괄호로 형변환한 것은
예외가 발생한다.
if(p1 is UnityCham) {} //형변환할 수 있다면 처리를 실행한다.
13.1 제네릭
제네릭은 다양한 형을 지정해도 같은 처리르 실행하고 싶을 때 사용한다.
예를 들어 서로 값을 교체하는 Swap메서드를 마드는데 정수든지 소수든지 문자열이든지
똑같은 처리를 하고 싶다고 가정해보자.
public void Swap(ref int x, ref int y) { var t = x; y = x; x = t; }
public void Swap(ref float x, ref float y) { var t = x; y = x; x = t; }
public void Swap(ref string x, ref string y) { var t = x; y = x; x = t; }
이럴 때는 아래코드와 같이 작성하면T가
적힌 부분에 어떤 형을 넣어도 똑같은 동작을 하게된다.
public void Swap<T>(ref T x, ref T y)
{
var t = x; y = x; x = t;
}
14.1 컬렉션
컬렉션은 한 마디로 오브젝트를 관리하는 방법이다. 컬렉션 중에는 제네릭으로 만들어진
것과 그렇지 않는 것이 있는데 제네릭으로 만들어지짖 않은 것을 사용할 때의 장점은
없으므로 반드시 제네릭 컬렉션을 사용하기 바란다. 자세한 내용은 아래 표에 나와있다.
// ArrayList list = new ArrayList(); //제네릭으로 만들어지지 않은 컬렉션은 사용하지 않는다.
List<string> list = new List <string>(); //제네릭으로 만들어진 컬렉션을 사용한다.
배열도 컬렉션의 일종이지만, 배열은 나중에 개수를 바꿀 수 없다는 것은 점이 다르다
var faces = new string[2]; // 배열은 처음에 정한 개수로 사용해야 한다
faces[0] = "angry";
faces[1] = "conf";
// faces[2] = "smile"; // 배열의 요소 개수가 2이므로 3을 넣으면 예외가 발생한다.
컬렉션 클래스인 List<T>를 사용하면 요소의 개수를 마음대로
추가할 수 있을 뿐만 아니라 항목도 자유롭게 추가할 수 있다.
var faces = new List<string>(); //컬렉션은 요소의 개수를 자유롭게 지정할 수 있다
faces.Add("angry");
faces.Add("conf");
if(flag) //요소 역시 마음대로 추가할 수 있다.
{
faces.Add("sap");
}
faces.Insert(2, "smile"); //코드가 진행되는 도중에도 추가할 수 있다.
예를 들어 아래코드와 같이 작성하면 2개 추가했을 때(Add 했을 때)와 같은 의미다.
var faces = new List<string> { "angry". "conf" };
15.1 foreach
foreach는 배열이나 컬렉션의 모든 요소를 하나씩 읽어들이기 위한 키워드다.
아래코드와 같이 배열 이름과 변수 이름만으로 내용을 하나씩 넣는다.
하지만 foreach를 사용해서 항목을 추가하거나 제거하는 것은 좋지않다.
var faces = new [] { "angry", "sap", "smile" };
foreach (string face in faces) //배열 이름과 변수 이름만 지정한다.
{
// faces[0] = 'aa'; //배열처럼 값을 수정할 수 없기 때문에 안전하다.
foreach (var x in faces) Console.WriteLine(x);
}
16.1 yield
유니티에는 코루틴이라는 기능이 있다. 코루틴은yieId return으로 처리를 멈춘 다음
다시 시작 할 때 그 다음 줄부터 처리를 진행하는 것을 의미한다.
코루틴은 StartCoroutine()메서드로 구현하는 것이 편리하다. 아래코드 에서는
WaitForSeconds()로 지정한 시간만큼 기다른 후 다음 행이 처리된다.
void Start()
{
StartCoroutine("Coroutine");
}
IEnumerator Coroutine ()
{
yieId return new WaitForSeconds(3);
yieId return new WaitForSeconds(3);
yieId return new WaitForSeconds(3);
}
코루틴은 유니티에만 있는 기능이지만,C#에서도 yieId를 사용한다.
예를 들어 foreach로 반복문을 돌리며 홀수를 반환하는 메서드라면
yieId를 사용해서 아래코드 처럼 구현 할 수 있다. foreach가 실행될 때
yieId return 값을 하나씩 반환한다.
public IEnumerable<int> GetOddNumber(int max)
{
for (int i = 1; i < max; i += 2) { yieeId return i; }
}
// 100까지 수 중에 홀수를 foreach 문으로 처리한다
foreach (var odd in GetOddNumber(100)) { }
17.1 위임
위임(delegate)이란 대리자라고도 하며’처리를 맡긴다’라는 뜻으로 메서드의 참조를
나타내는 형이다. 메서드 선언 앞에 delegate를 붙여서 사용한다.
delegate void Motion (float time) //인수의 형이 float이며 반환것이 없은 위임
위임으로 선언된 메서드 이름을 형으로 해서 인수와 반환값이 같은 메서드의 참조를
대입한다. 이 변수는 변수가 참조하는 메서드를 몇번이고 호출할 수 있다.
void Jump(float time) { } //Jump 메서드의 인수와 반환값은 다음 행에
//지시자로 선언된 Motion의 것과 같다.
Motion m = Jump; //Jump 메서드의 참조를 위임형 변수 m에 대입한다.
m(0.0f); //이렇게 호출하는 것은 Jump(0.0f);와 같다.
위임은 차조를 여러 개 대입할 수 있다. 이를 구현하려면 +=로 추가하면 된다.
이처럼 여러 개의 참조를 대입한 위임을 호출하면 할단된 순서대로 각각이 참조하는
메서드 의 실체가 호출된다
Motion m = Jump;
m += Wait;
m(0.0f); // Jump(0.0f), Wait(0.0),
C#에는 이미 정의된 위임형이 있으며 이를 사용하면 delegate를 선언하지 않아도 된다.
반환값이 없으면 Action을, 반환값이 있으면 Func를 사용한다.
// 직접 위임을 선언하지 않아도 대입할 수 있다.
Action<float> m = Jump;
m(0.0f); // Jump(0.0f),
18.1 람다 식
람다 식(Lamda expression)은 이름을 붙이지 않아도 되는 메서드다.
람다 식은 이름 말고도 생략할 수 있는 요소가 많아 매우 간단하게 쓸 수 있다.
예를 들어 x를 제곱하는 메서드를 람다 식으로 작성하면 아래코드와 같다.
int Sqrt(int x) { return x * x; } // 일반 메서드
x => x* x // 람다 식
람다 식을 쓸 때는 규칙이 있다. 우선 람다 연산사(⇒)가 필요하다.
그 다음은 아래코드를 순서대로 읽어보자
int Sqrt(int x) { return x * X; } // 일반 메서드
int Sqrt(int x) => { return x* X; } // 람다 식은 메서드 이름이 필요 없다
int (int x) => { return x* X; } // 좌변의 형은 추론되므로 형을 지정할 필요도 없다
(x) => { return x* X; } // 좌변에 인수가 하나면 괄호로 묶지 않아도 된다
x => x* X // 좌변이 한 행이면 중괄호와 return;은 필요없다
아래코드는 람다 식을 위임에 대입한 것이다.
Func<int, int> mul = x => x * x; // 람다 식 을 mul에 대입한다.
mul(2); // mul(2)로 4가 된다.
19.1 확장 메서드
확장 메서드(extension method)는 유니티와 닷넷 프레임워크에서 제공하는
기존 클래스를 바꾸지 않고도 메서드를 추가할 수 있는 기능이다. 예를 들어
확장 메서드를 사용하면 아래코드처럼 자신이 지정한 문자열을 마치 처음부터 선언되어
있던 참조인 것처럼 호출할 수 있다.
int x = "UnityChan".GetMyLength();
확장 메서드는 Static 클래스 안에 정적 메서드를 작성하고 첫 번째 인수에 this
와 형식을 지정한다
public static class MyExtensions
{
public static int GetMyLength(this string str) { return str.Length; }
}
정적 메서드로 선언하지만, 호출할 때는 인스턴스를 통해서 호출한다.
int x = MyExtensions.GetMyLength("UnityChan"); // 보통 메서드 호출
int x = "UnityChan".GetMyLength(); // 확장 메서드 호출
20.1 LINQ
LINQ는 다양한 데이터를 SQL 쿼리와 같이 손쉽게 가공하고 처리하는 기능이다.
예를 들어 배열에서 홀수만 가져올 때도 LINQ를 사용하면 단 한 줄로 처리할 수 있다.
var arry = new[] { 1, 2, 1, 5, 3, 6, 4 } ; // 숫자 배열
var k = from X in array where x % 2 == 1 select x; // 홀수 취득 {1, 1, 5, 3 }
LINQ를 작성하는 방법에는 두 가지가 있다.
하나는 위 두 번째코드와 같은 쿼리 구문이고,
다른 하나는 메서드 구분이다 둘 중 어느 것을 써도 차이는 없지만,
메서드 구문은 람다 식을 사용하므로 더 짧게 작성 할 수 있다.
var k = array.Where(x => x % 2 == 1); // 홀수만 가져옴 { 1, 1, 5, 3 }
LIMQ를 사용하면 코드가 간결해져서 의도를 파악하기 쉽다, 또한, 필터링이나
정렬 처리를 얼마든지 추가할 수 있다. 예를 들어 아래 코드와 같이 방금 가져온
홀수 집합에서 중복된 값을 삭제한뒤 정렬한 요소를 가져올 수 있다.
var k = array.Where(x => x % 2 == 1) // 홀수만 가져옴 {1, 1, 5, 3}
.Distinct() // 중복된 값 삭제 {1, 5, 3}
.OrderBy(x => x); // 정렬 {1, 3, 5}
LINQ 메서드에 관한 자세한 내용은 아래표를 참조하자
메서드 이름 | 설명 |
All | 시퀀스의 모든 요소가 특정 조건을 충족하는지 판단한다. |
Any | 시퀀스에 특정 요소가 있는지 판단한다. |
Average | 시퀀스에 있는 값의 평균을 계산한다. |
Cast | IEnumerable의 요소를 지정된 형으로 형변환한다. |
Concat | 두 시퀀스를 연결한다. |
Count | 시퀀스의 요소 개수를 반환한다. |
메서드 이름 | 설명 |
Distinct | 지정된 비교자를 사용해서 중복되지 않는 요소를 반환한다. |
ElementAt | 시퀀스에서 지정된 인덱스 위치에 있는 요소를 반환한다. |
Except | 지정된 비교자를 사용해서 두 개의 시퀀스의 차집합을 생성다. |
First | 시퀸스에서 첫 번재 요소를 반환한다. |
Last | 시퀸스에서 마지막 요소를 반환한다. |
Max | 시퀀스에서 최댓값을 반환한다. |
Min | 시퀀스에서 최솟값을 반환한다. |
OrderBy | 지정된 비교자를 사용해서 시퀀스의 요소를 오름차순으로 정렬한다. |
Range | 지정된 범위 안에서 정수 시퀀스를 생성한다. |
Repeat | 하나의 값이 반복되는시퀀스를 생성한다. |
Reverse | 시퀀스에 있는 요소 순서를 반전시킨다. |
Select | 시퀀스의 각 요소를 새 품에 투영한다. |
SelectMany | Select가 중첩된 기능이다. |
Skip | 시퀀스에서 지정된 개수만큼 요소를 건너뛰고 나머지 요소만 다룬다. |
Sum | 시퀀스 값을 합계를 계산한다. |
Take | 시퀀스 시작 위치에서 지정된 개수마늠 연속하는 요소를 반환한다. |
ToArray | IEnumerable<T>에서 배열을 생성한다. |
ToList | IEnumerable<T>로 List<T>를 생성한다. |
Where | 조건 서술부에 작성된 대로 시퀀스를 필터링한다. |
Single | 시퀀스에서 지정된 조건에 맞는 유일한 요소를 반환한다. 두 개 이상일 때는 예외가 발생한다. |
21.1 정리