728x90
반응형

이런 질문을 받은 적이 있다.

"C#에서 string을 비교할 때 ==을 많이 사용하셨는데 ==을 많이 사용하신 이유가 무엇인가요?"

그냥 같다 라는 의미로 사용한거지 별 다른 이유가 없었는데 면접관분께서 좋은 이야기를 해주셨다.

"C#에서 string, int등 상수를 비교 할 때는 Equals()를 사용하는게 좋다. ==은 참조를 비교하는거고 Equals는 값을 비교하는 것이다."

Equals를 알고 있었지만 해당 내용에 대해서는 처음 듣는 이야기여서 하나 더 배웠다는 생각이 든다.

예시를 들자면

public class EX : MonoBehaviour
{

	string ex1 = "Hello";
    object ex2 = new string("Hello");
    private void Ex()
    {
        if (ex1 == ex2)
        {
            // 의도하지 않은 참조가 발생했다고 오류 발생
            // 참조가 다르기 때문에 해당 부분은 출력되지 않음
            Debug.Log("True ex1 If1");
        }

        if (ex1.Equals(ex2))
        {
            // 참조가 다르지만 값이 같기 때문에 해당 부분 출력
            Debug.Log("True ex1 If2");
        }
    }
    
	StringBuilder sbText1 = new StringBuilder("Hello");
	StringBuilder sbText2 = new StringBuilder("Hello");
    private void Ex2()
    {
    
    	if (sbText1 == sbText2)
		{
        	// 참조가 다르기 때문에 해당 부분은 출력되지 않음
	    	Debug.Log("True ex2 If1");
		}
        
		if (sbText1.Equals(sbText2))
		{
        	// 참조가 다르지만 값이 같기 때문에 해당 부분 출력
    		Debug.Log("True ex2 If2");
		}
    }
}

해당 부분에서 이렇게 나뉜다. 의도하지 않은 조건식을 만들 수 있기 때문에 상수인 값을 비교한다면 Equals를 사용해야 할 것 같다.

string 뿐만 아니라 int, float등 다양한 자료형도 똑같다.

728x90
반응형

'C#' 카테고리의 다른 글

[C#] Count와 Length  (0) 2023.11.13
728x90
반응형

1. 백터의 내적과 외적을 게임에서 사용하는 경우

내적의 활용 - 앞뒤 판별

플레이어의 정면 벡터(forward vector)와 플레이어가 적을 향하는 벡터를 내적하여 나온 스칼라 값이 양수이면 적이 플레이어 방향벡터와 같은 방향(앞)에 있고 음수이면 반대 방향(뒤)에 있음

내적의 활용 - 시야각 판별

플레이어의 시야각 a가 적을 향하는 정면 벡터에서 플레이어가 적을 향하는 벡터까지의 각이 2/a보다 작으면 시야 이내에 있는것을 알 수 있음

외적의 할용 - 좌우 판별

플레이어의 정면 벡터와 플레이어가 적을 향하는 벡터를 외적하여 나온 노말 벡터를 업 벡터와 내적한 스칼라 값이 양수이면 같은 방향에 있고 아니면 다른 방향에 있음

2. Quaternion과 Euler, 짐벌락

Euler : 우리가 흔히 알고있는 x,y,z 3개의 축을 기준으로 회전시키는 각도계

짐벌락 : 같은 방향으로 오브젝트의 두 회전 축이 겹치는 현상

Unity에서는 오일러 앵글이 X,Y,Z 순서대로 계산되고 각 축을 독립적으로 판단해서 짐벌락이 발생하면 한 축에 대해서는 계산이 불가능함 이러한 현상을 해결하기 위해 나온 것이 쿼터니언

Quaternion : x,y,z,w로 이루어져 있음 (w = 스칼라)

방향과 회전을 다 표현할 수 있다. 하지만 쿼터니언의 회전은 180보다 큰 값을 표현할 수 없음

3. 머터리얼

머터리얼은 게임 오브젝트의 렌더링 효과를 결정하는 속성을 가진 컴포넌트, 쉐이더와 텍스쳐를 조합하여 오브젝트의 색상, 질감, 광택 등을 제어할 수 있음

4. 프리팹

게임 오브젝트를 미리 만들어 놓고 필요할 때마다 인스턴스화하여 사용할 수 있도록 하는 기능

프로젝트에 저장되어 있으며 필요할 때마다 가져와 사용할 수 있음, 이를 통해 게임 오브젝트의 중복 생성을 줄이고 효율적으로 사용

5. 쉐이더

쉐이더는 그래픽 처리 유닛에서 사용되는 프로그램으로 렌더징 엔진에서 빛, 색상, 질감등의 시각적요소를 계산하는데 사용, 쉐이더를 사용하여 오브젝트의 머터리얼에 적용하여 시각적인 효과 구현

6. 빌드

게임을 실행 가능한 상태로 만드는 작업, 플랫폼별로 빌드하여 사용자에게 제공

7. 콜리더와 리지드바디의 차이

콜리더는 충돌 감지를 위한 컴포넌트, 리지드바디는 물리 엔진과 상호작용하기 위한 컴포넌트

콜리더는 오브젝트가 다른 오브젝트와 충돌하는 것을 감지하고 이에 대한 이벤트 발생, 리지드바디는 물리 엔진에서 오브젝트의 물리적인 움직임을 제어하며 콜리더와 함께 사용하여 오브젝트간 충돌 및 반응 구현

8. 코루틴 vs 인보크

코루틴 : 유니티에서 비동기 처리를 구현하는 기능으로 메서드 실행을 일시 중지하고, 일정 시간이 지난 후에 중지한 부분부터 다시 실행하는 기능, 단일 쓰레드 형식, 오브젝트가 비활성화 되면 호출되지 않음

인보크 : 오브젝트가 비활성화 상태여도 호출, 매개 변수로 넘겨준 시간 만큼 지연 호출

9. 레이 캐스트

광선을 쏘아 해당 위치에 대한 정보를 가져오는 기능

10. 애니메이션 애니메이터

애니메이션은 게임 오브젝트의 동작을 미리 만들어 놓은 애니메이션 클립으로 제어 하는 기능

애니메이터는 애니메이션 클립을 제어하는 기능

11. 생명 주기

주로 물어보는 것들 위주

Reset - Awake - OnEnable - Start - FixedUpdate - OnTrigger - OnCollision - OnMouse - Update - yield null - yield WaitForSeconds - LateUpdate - Scene rendering - OnGUI - OnApplicationPause - OnDisable - OnApplicationQuit - OnDestroy

12. FixedUpdate, Update, LateUpdate

Update : 스크립트가 enabled 상태일때 매 프레임 마다 호출, 물리 효과가 적용되지 않은 오브젝트의 움직임이나 타이머, 키입력에 사용

FixedUpdate : Fixed Timestep에 설정된 값에 따라 일정한 간격으로 호출, 물리 효과가 적용된 오브젝트를 조정할 때 사용

LateUpdate : 모든 Update함수가 호출된 후 마지막으로 호출, 주로 오브젝트를 따라가는 카메라에 사용

13. 드로우 콜

CPU가 GPU에 렌더링 하라고 명령을 보내는 것

14. Rect Transform 컴포넌트 Anchros,Pivot

Anchros : UI의 원점 위치를 정함

Pivot : UI의 기준점을 정함

15. 캔버스

Screen Space - Overlay : UI 요소들이 스크린 상에만 존재, 3D 오브젝트 위에 그려져 덮어짐

Screen Space - Camera : 3D 좌표 상에서 위치값을 가지게 됨, UI요소(캔버스)보다 앞에 오게 배치할수 있음, 파티클이 이에 해당

World Space : UI를 3D 오브젝트 취급, 회전값 위치값 다 가질수 있음, 바닥이나 표면같은 곳, 증강현실 UI

728x90
반응형
728x90
반응형

1. 인터페이스와 가상 함수, 추상 함수

virtual(가상) 함수 : 하나의 기능을 하는 완전한 클래스, 파생된 클래스에서 상속하여 재정의가 가능하나 필수가 아님

abstract(추상) 함수 : 부모 클래스에서 정의만 하고 파생된 클래스에서 상속하여 반드시 재정의를 해야함

interface(인터페이스) : 추상클래스와 비슷하지만 멤버 변수를 사용할 수 없음, 서로 다른 계층이나 타입이더라도 같은 기능을 추가하고 싶을 때 사용 (예시 : 몬스터나 플레이어나 데미지를 입는건 마찬가지이므로 해당 함수를 가진 인터페이스를 사용할 수 있다.)

abstract vs interface

  interface abstract class
접근 지정자 public private, public, protect
구현 구현 불가 구현 가능
속도 상대적으로 느림 상대적으로 빠름
인스턴스화 사용 불가 사용 불가
필드 가질수 없음 필드와 상수 정의 가능
메소드 추상 메소드만 가능 일반 메소드도 있을수 있음
멤버 변수 사용 불가 사용 가능

2. 구조체와 클래스

구조체는 상속 불가, 값 타입, Stack에 할당

클래스는 상속 가능, 참조 타입, Heap에 할당

3. 상속성과 다형성

상속 : 부모 클래스로부터 필드, 메소드 등을 그대로 물려 받아 새로운 파생 클래스를 만드는 것

다형성 : 객체가 여러 형태를 가질수 있음

(예시 : 오버로딩을 통해 같은 이름의 메소드가 매개 변수의 개수나 타입이 다른경우)

public class EX : MonoBehaviour
{
	private void print(int ex)
    {
    	Debug.Log(ex);
    }
    
    private void print(string ex)
    {
    	Debug.Log(ex);
    }
}

 

 

(예시 : 오버라이딩을 통해 부모 클래스로 부터 물려받은 메소드를 재정의하여 사용하는 것)

public class EX : MonoBehaviour
{
	protected virtual void print()
    {
    	Debug.Log("EX Class : !");
    }
    
    protected abstract void printint ();
}

public class EX2 : EX
{
	protected override void print()
    {
    	base.print();
        Debug.Log("EX2 Class : @");
    }
    
    protected override void printint()
    {
    	Debug.Log("EX2 Class : 0");
    }
}

Output :
EX Class : !
EX Class : !
EX2 Class : @
EX2 Class : 0

4. 배열과 리스트

배열은 동적 할당 불가능, List는 동적 할당이 가능

배열은 데이터의 크기가 정해져 있음, 값을 중간에 넣고 뺄 때 크기를 늘리거나 줄이는 작업이 필요해서 많은 연산 필요

리스트는 데이터의 크기가 정해져 있지 않음, 값을 중간에 넣고 뺄 때 자유로움

5. namespace와 partial

클래스이름의 중복 방지 namespace

클래스의 크기가 커서 길어지는 경우 partial로 파일을 분할하여 동일한 클래스로 작성 가능

6. 객체 지향

SOLID (솔리드) 원칙

SRP(Single Responsibility Principle, 단일책임원칙) : 클래스는 하나의 목적만 가져야 하며, 클래스를 변경하는 이유도 하나여야 한다.

OCP(Open-Closed Principle, 개방폐쇠원칙) : 클래스는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다.

LSP(Liskov Substitution Principle, 리스코프치환원칙) : 상위 타입의  객체를 하위 타입으로 바꾸어도 프로그램은 일관되게 동작해야 한다.

ISP(Interface Segregation Principle, 인터페이스분리원칙) : 클라이언트는 이용하지 않는 메소드에 의존하지 않도록 인터페이스를 분리해야 한다.

DIP(Dependency Inversion Principle, 의존역전법칙) : 클라이언트는 추상화(인터페이스)에 의존해야 하며, 구체화(구현된 클래스)에 의존해서는 안된다.

7. 전역변수 vs 지역변수

전역 변수 : Heap 메모리에 저장되며 함수 외부에서 선언, 프로그램이 종료되기 전까지 파괴되지 않음

지역 변수 : Stack 메모리에 저장되며 함수 내부에서 선언, 해당 함수가 종료되면 파괴

8. delegate와 event

delegate : public으로 선언하면 클래스 외부에서 호출 가능, callback의 용도로 사용

event : public으로 선언해도 클래스 외부에서 호출 불가, 객체의 상태 변화나 사건의 발생을 알리는 용도로 사용

9. Boxing(박싱), UnBoxing(언박싱)

Boxing : 값 타입의 객체를 참조 타입으로 변환하는 작업 → Stack에 있던 값 타입의 객체를 Heap으로 이동할 때 복사가 일어난다. → Heap 영역에 복사된 영역을 참조 타입이 가르키게 된다(주소값 할당)

UnBoxing : 객체 타입의 객체를 값 타입으로 변환하는 작업 → Heap 영역에 있던 데이터를 Stack 영역으로 복사

10. object 타입에 value 타입을 대입하면

value 타입을 참조 타입으로 박싱한다. Stack영역에 있던 value를 Heap으로 복사 후 주소값을 할당

11. 세대별 GC(Garbage Collection)

0세대 : GC를 한번도 겪지 않은 갓 생성된 객체

1세대 : GC를 1회 겪은 객체

2세대 : GC를 2회 겪은 객체

세대가 낮은 메모리부터 해제를 해준 다음 메모리 컴펙션

2세대 GC를 할 시 Full Garbage Collection이라 하고 전체 Heap에 대하여 GC하는 것을 의미

세대를 나누는 근거는?

최근에 생성된 객체일수록 생명주기가 짧을 가능섶이 높고 오래된 객체일수록 생명주기가 길 가능성이 높음

최근에 생성된 객체끼리는 서로 연관성이 높을 수 있으며 비슷한 시점에 자주 액세스 됨

일부분 Heap에 대해 GC하는 것이 전체 GC를 하는것 보다 빠름

12. LOH, SOH

LOH : Large Object Heap 으로 CLR(Common Language Runtime)에서 용량이 큰(83kb이상) 객체에 사용되는 Heap

SOH : 그 이하에 평소에 사용되는 Heap

LOH는 GC시 2세대로 간주, 메모리 해제 후 메모리 컴펙션을 진행하지 않으므로 메모리 내부 단편화가 발생할 수 있음

13. Call by Value vs Call by Reference

Call By Value : 인자로 받은 값을 복사하여 처리, 넘어온 값을 증가시키거나 감소시켜도 원래의 값 보존, 값을 복사하여 사용하기 때문에 메모리 사용량이 상대적으로 늘어나 느림

Call By Reference : 인자로 받은 값의 주소를 참조하여 처리, 넘온 값을 참조하여 직접 값에 영향을 미침, 값을 참조하여 사용하기 때문에 메모리 사용량이 상대적으로 적어 빠름

14. 프로세스, 쓰레드

프로세스 : 메모리에 올라와 실행되고 있는 프로그램의 인스턴스

운영체제로부터 독립된 메모리 영역을 할당받음, 서로 독립적이라 통신이 어려움 1개 이상의 쓰레드를 가지고 있음

쓰레드 : 프로세스 내에서 할당받은 자원을 이용해 동작하는 실행 단위

쓰레드는 프로세스 내에서 Stack영역만 할당 받고 Code, Data, Heap 영역은 서로 공유, 프로세스의 자원을 공유하기 때문에 다른 쓰레드에 의한 결과를 즉시 확인 가능(통신이 쉬움)

멀티 프로세스 : 하나의 프로그램을 여러 개의 프로세스로 구성하여 1개의 작업을 처리하도록 하는 것

하나의 프로세스가 죽어도 다른 프로세스에 영향이 없음

멀티 쓰레드 : 하나의 프로그램을 여러 개의 쓰레드로 구성하여 1개의 작업을 처리하도록 하는 것

자원을 공유하기 때문에 효율적으로 사용 가능, 하나의 쓰레드의 문제가 생기면 전체 프로세스가 영향을 받음, 하나의 자원에 여러 쓰레드가 동시에 접근하면 동기화 문제가 발생할 수 있음

15. 데드락(교착상태)

한정된 자원을 여러 프로세스가 사용하고자 할 때 발생하는 상황으로 프로세스가 자원을 얻기위해 영구적으로 기다리는 상태

A라는 자원을 가진 프로세스1(P1)이 B라는 자원을 가진 프로세스2(P2)의 B가 필요하고 P2는 A가 필요할 때 데드락 발생,

뮤텍스나 세마포어 등으로 문제를 예방

16. 싱글톤 패턴 사용 이유

싱글톤 패턴 : 단 하나의 인스턴스만 생성하여 사용하는 디자인 패턴

사용하는 이유 : 단 하나의 인스턴스만 생성하는 것을 보증하고 싶은 경우 (GameManager), 메모리 낭비 방지, 인스턴스가 메모리에 올라와 있으면 언제든지 사용할수 있기 때문

728x90
반응형
728x90
반응형

해당 부분은 꽤 많이 들었던 소리다. foreach문을 사용하면 가비지가 발생하는 이슈가 있다. 최적화를 위해서는 foreach, Invoke, GetComponent 등을 자제하라는 말을 들었는데

Unity C#에서 foreach는 왜 가비지가 발생할까?

결론부터 말하자면 Unity 5.5버전 이상에서는 가바지가 발생하는 이슈가 수정되었다.

우선 원인은 Mono C# Unity 5.5 미만 버전에서는 foreach loop가 종료되면 값을 강제로 박싱하고 값 형식의 열거자를 생성하였는데 해당 열거자를 생성할 경우 loop가 종료되는 시점에 IDisposable 인터페이스를 구현 해야 했다. 인터페이스는 참조 형식이기 때문에 값 형식을 인터페이스로 변환하는 중에 박싱이 발생하게 되어서 가비지가 발생한다.

using (var enumerator in list.GetEnumerator())
{
	while(enumerator.MoveNext())
    {
    	int current = enumerator.Current;
    }
}

Enumerator enumerator = list.GetEnumerator();
try
{
	while(enumerator.MoveNext())
    {
    	int current = enumerator.Current;
    }
}
finally
{
	var disposable = (System.IDisposable)enumerator;
    disposable.Dispose();
}

foreach문의 내부를 보면 var disposable = (System.IDisposable)enumerator; 에서 박싱을 하고 있다.

단, List<>와 같은 명시적인 Collection을 foreach한 경우에는 가비지가 발생하지는 않지만 IEnumerable과 같이 인터페이스를 사용하는 경우 가비지가 발생한다고 합니다.

 

최적화 참고자료 : https://andrewfray.wordpress.com/2013/02/04/reducing-memory-usage-in-unity-c-and-netmono/

728x90
반응형
728x90
반응형

C#을 자주 사용하면 배열과 리스트 자료구조를 많이 사용하면서 자주보는 Count와 Length일텐데 해당 부분이 왜 다른지 한 번 생각해보기로 했다.

우선 배열(Array)의 경우 Length를 사용한다. 배열은 데이터가 메모리에 순차적으로 정리되므로 Length인 길이를 사용한다.

반면에 리스트(List)의 경우 Count를 사용한다. 리스트는 데이터들이 메모리에 마구잡이로 존재하기 때문에 Count인 갯수로 사용한다.

배열은 크기가 고정되어 있지만 인덱스에 접근하기 편하다는 장점이 있고 크기가 고정되어 있는 만큼 중간에 값을 넣고 빼기에 연산이 많이 들어가 어렵다는 단점이있는 반면, 리스트는 크기가 유연하게 변한다는 장점이 있고 배열에 비해 느리다는 단점이 있다. 상황에 따라 해당 자료구조를 이용하면 된다.

728x90
반응형

'C#' 카테고리의 다른 글

[C#] C#의 Equals()와 ==에 관하여  (0) 2023.11.21
728x90
반응형
[SerializeField]
private Image Bar;       
[SerializeField]
private RectTransform SpeechBubble;

private float count;
private float maxcount

Bar.fillAmount = count / maxcount;

float width = Bar.GetComponent<RectTransform>().rect.width;
Vector3 tempV = SpeechBubble.anchoredPosition;
tempV.x = -width / 2;
tempV.x += width * Bar.fillAmount;
SpeechBubble.anchoredPosition = tempV;

유니티에서 FillAmount를 사용하는 경우가 많은데 FillAmount의 끝자리에 특정한 오브젝트가 위치하도록 하는 코드이다.

 

728x90
반응형
728x90
반응형

Unity에서 Unity WebRequest를 사용해 Http 통신하여 웹 서버에서 데이터를 요청할 수 있다. 사용 하기전 using UnityEngine.Networking를 꼭 써주어야 한다.

데이터를 크롤링할때 json 형태나 html 형태로 크롤링 하는데 json이면 Newtonsoft 라이브러리를 사용해서 파싱, html은 Package Html Agility Pack 라이브러리를 사용해서 파싱한다. NuGet에서 다운 받을수 있다.

   public void LoadJson(string url, string folderName, string dataName) => StartCoroutine(LoadJsonFile(url, folderName, dataName));

    IEnumerator LoadJsonFile(string url, string folderName, string dataName)
    {
        UnityWebRequest request = UnityWebRequest.Get(url);

        yield return request.SendWebRequest();

        string dataAsJson = request.downloadHandler.text.ToString();

        if (request.isDone && request.error == null)
        {
            UIManager.Instance.LoadJsonForAndroid(dataAsJson);
        }
        else
        {
            UIManager.Instance.errorPopup.SetMessage("통신 오류 또는 해당 스테이지는 존재하지 않습니다.");
        }
        request.Dispose();
    }

이런식으로 서버에 요청하면 데이터를 받을 수 있다. 해당 url이 없거나 권한이 없으면 통신이 실패한다. (404, 403에러) Get과 Post가 있는데 Get은 서버에 그냥 데이터를 요청하는 방식이고, Post는 WWW Form을 사용해 필요한 데이터를 입력해 데이터를 요청한다.

WebRequest를 사용하면 Dispose를 꼭 해주자 관리되지 않는 리소스는 .NET의 가비지 컬렉션이 자동으로 관리해주지 않아서 명시적으로 Dispose를 호출하여 해당 리소스를 해제하지 않으면 리소스 누출이 발생할 수 있다!

나는 회사에서 Unity WebRequest의 Post를 자주 사용하는데 

    public delegate void NetworkResultCallback(string result);
    
    public void StartNetworking(bool networkingCheck, string url,
    	List<IMultipartFormSection> form, NetworkResultCallback networkResultCallback)
    {
        if (Application.internetReachability == NetworkReachability.NotReachable &&
        networkingCheck)
        {
            UIManager.Instance.errorPopup.SetMessage("통신시작 과정에서 통신 실패.");
        }
        else
        {
            StartCoroutine(PostNetworking(url, form, networkResultCallback));
        }
    }

    IEnumerator PostNetworking(string url, List<IMultipartFormSection> form,
    	NetworkResultCallback networkResultCallback)
    {
        UnityWebRequest webRequest = UnityWebRequest.Post(_serverURL + url, form);
        webRequest.SetRequestHeader("Accept", "application/json");

        yield return webRequest.SendWebRequest();

        if (webRequest.error != null)
        {
            UIManager.Instance.errorPopup.SetMessage("보내는 과정에서 통신 실패.");
        }
        else
        {
            networkResultCallback?.Invoke(webRequest.downloadHandler.text);
        }
        webRequest.Dispose();
    }

 

이런식으로 IMultipartFormSection 리스트를 통해 파라미터 정보를 보낸다음에 데이터를 요청한다.

728x90
반응형

+ Recent posts