String 데이터 타입은 immutable Type인데 코드 상에서는 대입 연산자가 오버 로딩되어 있어 마치 mutable Type처럼 사용이 가능합니다. 하지만 내부적으론 새로운 공간에 새로운 값을 할당하여 오버헤드가 발생을 하게 됩니다.

 

이때 버퍼를 가지고 있는 StringBuilder 클래스를 사용하면 mutable Type으로 사용이 가능합니다. 따라서 String과 StringBuilder의 대입 연산을 반복적으로 돌려서 서로의 오버헤드를 알아보고자 합니다. 

 

using System.Text;
using System.Diagnostics;

private void OverheadTest()
{
	string test1 = "";

    Stopwatch time1 = new Stopwatch();
    time1.Start();

   	for (int i = 0; i < 100000; i++)
   	{
   	    test1 += i.ToString();
   	}

   	time1.Stop();

   	UnityEngine.Debug.Log("TEST 1 Time : " + time1.ElapsedMilliseconds);

   	StringBuilder test2 = new StringBuilder();

   	Stopwatch time2 = new Stopwatch();
   	time2.Start();

   	for (int i = 0; i < 100000; i++)
   	{
   	    test2.Append(i.ToString());
   	}

   	time2.Stop();

   	UnityEngine.Debug.Log("TEST 2 Time : " + time2.ElapsedMilliseconds);
}

실행 결과 --------
TEST 1 Time : 59987
TEST 2 Time : 74

 

각 각 10만번의 반복문을 돌린 결과 생각보다 큰 차이가 있었습니다. 따라서 문자열의 할당과 관련된 작업을 지속적이고 대량으로 할 경우 StringBuilder를 사용하면 될 것 같습니다.

 

블로그 이미지

NIA1995

,

정수형 변수
short : 16bit 부호가 있는 정수
int : 32bit 부호가 있는 정수
long : 64bit 부호가 있는 정수, L 접미어를 붙여줘야 한다.
 
ushort : 16bit 부호가 없는 정수, 0과 양수만 가능
uint : 32bit 부호가 없는 정수, 0과 양수만 가능, U 접미어를 붙여줘야 한다.
ulong : 64bit 부호가 없는 정수, 0과 양수만 가능, UL 접미어를 붙여줘야 한다.


실수형 변수
float : 32bit 실수, f 접미어를 붙여줘야 한다.
double : 64bit 실수, d 접미어 거를 수 있다.
decimal : 128bit 실수,  m 접미어를 붙여줘야 한다.

 

 

그 외
char : 16bit UniCode 문자
byte : 8bit, 1바이트 표현 단위, 예) 0x46

↑ Value 형식의 데이터 타입

string : 문자열
object : C#의 최상위 개체

↑ Reference 형식의 데이터 타입으로 Null 체크 가능

Value 형식의 경우 기본적으로 Null 체크가 불가능하지만, Nullable 변수를 사용하면 체크가 가능하다.

예) int? ix = null;

if(ix == null)
{
...
}  

블로그 이미지

NIA1995

,

using 키워드에는 널리 알려진 네임스페이스에서 정의된 형식을 가져오는 기능 이외에도 다양한 기능이 존재합니다. 공식 문서를 살펴보며 간단하게 살펴보겠습니다.

 

1. 개체가 삭제될 끝에서 범위를 지정 /* docs.microsoft.com/ko-kr/dotnet/api/system.idisposable?view=net-5.0 */

   1) 이 인터페이스의 주요 용도는 관리되지 않는 리소스를 해제하는 것입니다.

   2) 가비지 수집기는 창 핸들 또는 열린 파일 및 스트림과 같은 관리 되지 않는 리소스에 대해 알지 못합니다.

   3) 따라서 가비지 수집기와 함께 관리 되지 않는 리소스를 명시적으로 해제합니다.

 

한번 더 설명하면 C#에서는 가비지 콜렉터가 작동하는데 항상 만능은 아닙니다. 따라서 명시적으로 리소스를 해제해야 할 때 using 키워드를 사용하여 IDisposable을 적용할 수 있습니다.

 

void ReadData() {
            using(BinaryReader binaryReader = new BinaryReader(new FileStream(src, FileMode.Open))) {
                int intNum = binaryReader.ReadInt32();
                float floatNum = binaryReader.ReadSingle();
                string stringData = binaryReader.ReadString();
                bool boolData = binaryReader.ReadBoolean();
                
                /* binaryReader.Close()가 자동 호출 */
            }
        }

 

위의 코드와 같이 using 문을 사용하면 자칫 실수하고 넘어갈 수도 있는 binaryReader의 리소스를 자동으로 해제할 수 있습니다.

 

2. 네임스페이스의 별칭 제작

 

간단하게 기존 네임 스페이스의 별칭을 제작하여 사용할 수 있는 기능입니다.

 

using PD = PersonalData;

class PersonalData
{
	…….
}

 

위의 코드와 같이 using 문을 사용하면 PersonalData 대신 PD로도 클래스에 접근이 가능합니다.

 

3. using static 지시문 /* docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/using-static */

   1) 일반적으로 정적 멤버를 호출하기 위해 멤버의 이름과 함께 형식 이름을 사용합니다.

   2) 이러한 정적 멤버의 호출이 반복되는 경우 코드가 복잡하고 난해해질 수 있습니다.

   3) 이때, using static 키워드를 사용하면 깔끔한 코드를 생성할 수 있습니다.

 

using System;

public class Circle
{
   public Circle(double radius)
   {
      Radius = radius;
   }

   public double Radius { get; set; }

   public double Diameter
   {
      get { return 2 * Radius; }
   }

   public double Circumference
   {
      get { return 2 * Radius * Math.PI; }
   }

   public double Area
   {
      get { return Math.PI * Math.Pow(Radius, 2); }
   }
}

-----------------------------------------------------

using System;
using static System.Math;

public class Circle
{
   public Circle(double radius)
   {
      Radius = radius;
   }

   public double Radius { get; set; }

   public double Diameter
   {
      get { return 2 * Radius; }
   }

   public double Circumference
   {
      get { return 2 * Radius * PI; }
   }

   public double Area
   {
      get { return PI * Pow(Radius, 2); }
   }
}

 

위의 코드와 같이 반복적인 형식 이름의 접근이 사라졌습니다. 하지만 다중으로 using static 키워드를 사용했을 경우 각 클래스에서 같은 이름의 멤버들이 존재할 수도 있으므로 주위가 필요합니다.

블로그 이미지

NIA1995

,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

namespace Serialization
{
    [Serializable]
    class Playable
    {
        public string name;
        public int level;
        public long exp;

        public Playable()
        {
            name = "";
            level = 1;
            exp = 0;
        }

    }


    class Program
    {
        const string fileName = "SaveData.txt";

        static void Main(string[] args)
        {
            /* 객체 초기화 */
            Playable[] characters = new Playable[3];

            for(int i = 0; i < 3; ++i)
            {
                characters[i] = new Playable();
            }

            characters[0].name = "아라곤";
            characters[0].level = 100;
            characters[0].exp = 2000;

            characters[1].name = "레골라스";
            characters[1].level = 98;
            characters[1].exp = 1720;

            characters[2].name = "김 리";
            characters[2].level = 99;
            characters[2].exp = 1890;

            /* 직렬화 후 데이터 저장 */
            FileStream writeStream = new FileStream(fileName, FileMode.Create);

            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(writeStream, characters);

            writeStream.Close();

            /* 직렬화 된 데이터 읽기 */
            FileStream readStream = new FileStream(fileName, FileMode.Open);

            BinaryFormatter binaryFormatter2 = new BinaryFormatter();

            characters = (Playable[])binaryFormatter2.Deserialize(readStream);

            for(int i = 0; i < characters.Length; ++i)
            {
                Console.WriteLine("Name : {0} \t Level : {1} \t Exp : {2} \t", characters[i].name, characters[i].level, characters[i].exp);
            }

            readStream.Close();
        }
    }
}

해당 예제에서는 직렬화가능 속성(Serialization Attribute) 속성을 사용하여 직렬화를 테스트 하였습니다. 기본적으로 이보다 더 많은 제어가 필요한 경우 ISerializable 인터페이스를 상속받아 구현할 수 있습니다. 해당 코드에서는 사용하지 않았지만 ISerializable 인터페이스를 사용하는 경우 GetObjectData() 함수를 재정의하여 데이터 직렬화를 효과적으로 진행할 수 있습니다.

블로그 이미지

NIA1995

,

파일 입출력을 공부하다가 두 클래스의 함수와 프로퍼티를 나눠서 사용하는 예제를 보았는데 차이점이 있는가 궁금해서 찾아보았습니다. 결론적으로 두 클래스의 함수와 프로퍼티는 동일한 기능을 합니다.

 

단지 함수(Directory.GetCurrentDirectory, Directory.SetCurrentDirectory)와 프로퍼티(Environment.CurrentDirectory)로 나누어져 있습니다. 따라서 편하신 방법을 골라 사용하시면 될 것 같습니다.

 

string directoryPath = Directory.GetCurrentDirectory();
string environmentPath = Environment.CurrentDirectory;

Console.WriteLine("DirectoryPath : {0}", directoryPath);
Console.WriteLine("EnvironmentPath : {0}", environmentPath);

-------------------------------------------------------
DirectoryPath : D:\ConsoleProject\Test\Join\bin\Debug
EnvironmentPath : D:\ConsoleProject\Test\Join\bin\Debug

'프로그래밍 > C#' 카테고리의 다른 글

[C#] using 키워드의 다양한 사용처  (0) 2021.04.02
[C#] 직렬화(Serialization)  (0) 2021.04.02
[C#, LINQ] 내부 조인과 외부 조인  (0) 2021.03.31
[C#] Boxing과 Unboxing  (0) 2021.03.15
[C#] 값형식과 참조형식의 예시  (2) 2021.03.15
블로그 이미지

NIA1995

,

INNER JOIN

 

LINQ에서 내부 조인은 기본적으로 두 개의 데이터들 사이에서 일치하는 데이터들만 연결시켜 반환합니다. 첫 번째 데이터를 기준으로 두 번째 데이터를 equals를 사용해 비교하여 일치하는 데이터들을 모아 반환합니다.

 

var InnerJoin =
	from a in A
    	join b in B a.variables equals b.variables

 

A라는 데이터 원본에서 a라는 범위 변수를 뽑아오고, 두 번째 B라는 데이터 원본에서 b라는 범위 변수를 뽑아옵니다. 이후, equals에서 두 데이터를 비교하여 일치하는 데이터를 뽑아냅니다.

 

OUTER JOIN

 

외부 조인은 내부 조인과 달리 기준이 되는 데이터는 데이터의 일치 여부와 상관없이 모두 다 포함이 됩니다. 대신 일치하지 않는 데이터가 없는 경우에는 그 부분을 빈 값으로 처리하고, 임시 컬렉션에서 DefaultIsEmpty 연산을 통해 빈 값을 채워 줄 수 있습니다.

 

var OuterJoin =
	from a in A
    	join b in B a.variables equals b.variables into data
        from b in data.DefaultIfEmpty(new Class() { 데이터 삽입 })

 

실습

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Join
{
    struct Idol
    {
        public string name { get; set; }
        public string groupName { get; set; }

        public int age { get; set; }
        public int height { get; set; }

        public Idol(string _name, string _groupName, int _age, int _height)
        {
            name = _name;
            groupName = _groupName;
            age = _age;
            height = _height;
        }
    }

    struct Star
    {
        public string name { get; set; }
        public string team { get; set; }
 
        public int age { get; set; }
        public int height { get; set; }


        public Star(string _name, int _age, int _height, string _team = null)
        {
            name = _name;
            age = _age;
            height = _height;
            team = _team;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Idol[] Izone =
            {
                new Idol("권은비", "아이즈원", 25, 160),
                new Idol("김민주", "아이즈원", 19, 163),
                new Idol("조유리", "아이즈원", 19, 162),
                new Idol("최예나", "아이즈원", 21, 162),
                new Idol("김채원", "아이즈원", 20, 163),
                new Idol("강혜원", "아이즈원", 21, 163),
                new Idol("장원영", "아이즈원", 16, 175),
                new Idol("안유진", "아이즈원", 17, 169),
                new Idol("이채연", "아이즈원", 21, 165),
                new Idol("사쿠라", "아이즈원", 23, 163),
                new Idol("나코",   "아이즈원", 19, 150),
                new Idol("히토미", "아이즈원", 19, 158),
            };

            Star[] AKB48 =
            {
                new Star("사쿠라", 23, 163, "AKB48"),
                new Star("나코", 19, 150, "AKB48"),
                new Star("히토미", 19, 158, "AKB48"),
            };

            /* 내부 조인 */
            var InnerJoinData =
                from profile in Izone
                join item in AKB48 on profile.name equals item.name
                select new
                {
                    Name = profile.name,
                    GroupName = profile.groupName,
                    Age = profile.age,
                    Height = profile.height,
                };

            foreach(var item in InnerJoinData)
            {
                Console.WriteLine("이름 : {0}, \t 그룹 : {1}, \t 나이 : {2}, \t 키 : {3}", item.Name, item.GroupName, item.Age, item.Height);
            }

            Console.WriteLine();

            /* 외부 조인 */
            var OuterJoinData =
                from profile in Izone
                join item in AKB48 on profile.groupName equals item.team into ps
                from item in ps.DefaultIfEmpty(new Star() { team = "IZONE" })
                select new
                {
                    Name = profile.name,
                    Age = profile.age,
                    GroupName = item.team,
                    Height = profile.height,
                };

            foreach (var item in OuterJoinData)
            {
                Console.WriteLine("이름 : {0}, \t 그룹 : {1}, \t 나이 : {2}, \t 키 : {3}", item.Name, item.GroupName, item.Age, item.Height);
            }
        }
    }
}

-----------------------------------------------------------------

이름 : 사쿠라,   그룹 : 아이즈원,        나이 : 23,      키 : 163
이름 : 나코,     그룹 : 아이즈원,        나이 : 19,      키 : 150
이름 : 히토미,   그룹 : 아이즈원,        나이 : 19,      키 : 158

이름 : 권은비,   그룹 : IZONE,   나이 : 25,      키 : 160
이름 : 김민주,   그룹 : IZONE,   나이 : 19,      키 : 163
이름 : 조유리,   그룹 : IZONE,   나이 : 19,      키 : 162
이름 : 최예나,   그룹 : IZONE,   나이 : 21,      키 : 162
이름 : 김채원,   그룹 : IZONE,   나이 : 20,      키 : 163
이름 : 강혜원,   그룹 : IZONE,   나이 : 21,      키 : 163
이름 : 장원영,   그룹 : IZONE,   나이 : 16,      키 : 175
이름 : 안유진,   그룹 : IZONE,   나이 : 17,      키 : 169
이름 : 이채연,   그룹 : IZONE,   나이 : 21,      키 : 165
이름 : 사쿠라,   그룹 : IZONE,   나이 : 23,      키 : 163
이름 : 나코,     그룹 : IZONE,   나이 : 19,      키 : 150
이름 : 히토미,   그룹 : IZONE,   나이 : 19,      키 : 158
블로그 이미지

NIA1995

,

C# 프로그래밍에서는 Boxing과 Unboxing의 개념이 존재합니다. 닷넷 프레임워크에서 모든 타입은 Object 타입을 상속받게 됩니다. Boxing의 경우 값 형식의 데이터를 더 큰 영역인 Object 형식 또는 이 값 형식에서 구현된 임의의 인터페이스 형식으로 변환하는 과정을 의미합니다. 이때 Boxing하게 되는 값은 System.Object 인스턴스 내부에 래핑이 되고 에 저장되어 관리되게 됩니다.

 

int i = 123;
object o = i; // Boxing
int j = (int)o; // unboxing

https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/types/boxing-and-unboxing

 

Unboxing의 과정은 이와 반대로 암시적인 값을 다시 명확하게 Object 형식에서 값 형식으로 변환하는 목적의 과정입니다. 두 과정 모두 상당한 성능 소모가 필요합니다. Boxing의 경우 새로운 개체를 할당하고 생성해야 하며, Unboxing의 경우 캐스트 계산 과정이 필요합니다. 따라서 꼭 필요한 경우를 제외하고는 사용을 자제하는 편이 성능에 좋습니다.

 

 

블로그 이미지

NIA1995

,

- 값형식(Value) : 숫자, char, bool, enum 등

- 참조형식(Reference) : string, 배열, class, interface, delegate 등

'프로그래밍 > C#' 카테고리의 다른 글

[C#, LINQ] 내부 조인과 외부 조인  (0) 2021.03.31
[C#] Boxing과 Unboxing  (0) 2021.03.15
[C#] var 데이터 형식  (0) 2021.03.15
[C#] SerializableAttribute 클래스  (0) 2021.01.01
[C#] Null 조건 부 연산자  (1) 2020.12.26
블로그 이미지

NIA1995

,