BCL 클래스 문법 정리
목차
시간
using System.Diagnostics; //타이머를 쓰려면 선언해야 한다.
namespace ConsoleApplication1
{
class TimeEx
{
public static void Main()
{
DateTime now = DateTime.Now;
Console.WriteLine(now); //현재 시간 출력
Console.WriteLine(now.Ticks); //기준시간 1년 1월 1일. 유닉스나 자바에선 1970년 1월 1일
DateTime utcNow = DateTime.UtcNow;
Console.WriteLine(utcNow); //협정 세계시 출력 .Local로 하면 지역시간 반영
DateTime myBirthday = new DateTime(now.Year, 1, 24);
Console.WriteLine(myBirthday.Kind); //지정하지 않았으므로 Unspecified 출력
DateTime endOfYear = new DateTime(DateTime.Now.Year, 12, 31);
TimeSpan dayGaps = endOfYear - now; //DateTime의 빼기연산해서 나온 결과값은 TimeSpan으로 나온다.
Console.WriteLine(dayGaps.Days); //Day외에도 시간, 분, 초 , 밀리초 단위도 출력 가능
DateTime before = DateTime.Now; //Sum() 메서드의 수행 시간을 측정하는 부분
Sum();
DateTime after = DateTime.Now;
long gap = after.Ticks - before.Ticks; //Ticks의 기준은 100ns.
Console.WriteLine(gap);
Stopwatch st = new Stopwatch(); //스탑워치 기능도 쓸 수 있다.
st.Start();
Sum();
st.Stop();
Console.WriteLine("total Ticks : " + st.ElapsedTicks);
}
private static int Sum()
{
int sum = 0;
for (int i = 0; i < 1000000; i++)
{
sum += i;
}
return sum;
}
}
}
문자열 처리
String
class StringEx
{
public static void Main()
{
string txt = "Hello World!";
string[] stringArr;
Console.WriteLine(txt.Contains("Hello")); //true
Console.WriteLine(txt.Contains("WORLD")); //false
Console.WriteLine(txt.EndsWith("rld!")); //true
Console.WriteLine(txt.EndsWith("Hello")); //false
Console.WriteLine(txt.GetHashCode()); //해시값 반환
Console.WriteLine(txt.IndexOf("World!")); //해당 문자열 포함 시 그 문자열의 인덱스 리턴, 아니면 -1
Console.WriteLine(txt.IndexOf("Dog")); // -1
Console.WriteLine(txt.Replace("World", "c#")); //앞 문자열을 뒷 문자열로 대체 후 전체 리턴 Hello c#! 출력
stringArr = txt.Split('o'); //'o'를 기준으로 나눈 문자열들을 배열로 리턴
OutputArrayString(stringArr);// "Hell", " W", "rld!" 출력
Console.WriteLine(txt.StartsWith("Hel")); //true
Console.WriteLine(txt.StartsWith("hel")); //false
Console.WriteLine(txt.Substring(4)); //4번째부터 끝까지 출력. "o World!" 출력
Console.WriteLine(txt.Substring(4, 2)); //4번째부터 길이 2만큼 "o " 출력
//Console.WriteLine(txt.Substring(4, 50)); //ArgumentOutOfRangeException 발생
Console.WriteLine(txt.ToLower()); //소문자 변환 후 리턴
Console.WriteLine(txt.ToUpper()); //대문자 변환 후 리턴
Console.WriteLine(txt); //변환 내용이 유지되는 것은 아니다. 그대로 Hello World! 출력
Console.WriteLine(txt.Trim('!', 'H')); //문자열의 앞뒤에 해당 문자가 있으면 삭제
//매개변수 입력하지 않으면 앞뒤에 있는 공백 삭제
Console.WriteLine(txt.Length); //문자열의 길이 리턴, 12
Console.WriteLine(txt != "Hello World!"); //false
Console.WriteLine(txt == "Hello World!"); //true;
Console.WriteLine(txt[4]); //해당 위치에 있는 문자 반환
int a = 100;
int b = 50;
int formatInt = 123456;
double formatDouble = 123546.789;
string formatTxt = String.Format("A의 값은 {0}입니다. B의 값은 {1}이구요, 이 둘을 더한 값은 {2}입니다.", a, b, a + b);
Console.WriteLine(formatTxt);
formatTxt = String.Format("A의 값은 {0}입니다. B의 값은 {1}이고,, 이 둘을 더한 값은 {2}입니다. 뺀 값은 {3}입니다", a, b, a + b);
//매개변수 개수보다 인덱스가 더 많으면 예외 발생.
//인덱스 [,정렬] [:형식] 순으로 지정한다.
Console.WriteLine("정수 형식 : {0,20:d10}", formatInt);
//정수 형식. 123456에 10자리를 맞추기 위해 0000123456이 출력된다.
Console.WriteLine("숫자 형식 : {0,20:n10}", formatDouble);
//숫자 형식. 123456.789에 .789를 포함해서 10자리 소수점이 맞춰진다.
Console.WriteLine("퍼센트 형식 : {0,20:p3}", a);
//백분율 형식.100으로 곱하고 백분율 기호와 함께 표시된다. p뒤의 숫자는 소수점 자리수를 의미한다.
Console.WriteLine("16진수 형식 : {0,20:x10}", formatInt);
//16진수 형식. 10자리를 맞추기 위해 앞에 0이 표시된다.
}
private static void OutputArrayString(string[] arr)
{
foreach (string txt in arr) Console.WriteLine(txt);
}
}
StringBuilder
class StringBuilderEx
{
public static void Main()
{
string txt = "Hello World!";
string txt2 = "Hello World!";
Stopwatch st = new Stopwatch();
st.Start();
for (int i = 0; i < 300000; i++)
{
txt = txt + "1"; //매번 늘어난 크기의 공간을 새롭게 할당하기 때문에, 30만번의 메모리 할당과 복사 때문에 많은 시간이 걸린다.
}
st.Stop();
Console.WriteLine("String으로 작업하는데 걸린 시간 : " + st.Elapsed); //18초 정도 걸린다.
StringBuilder sb = new StringBuilder(txt2);
//StringBuilder 내에 일정한 메모리를 할당하고, txt2를 복사한다. 공간이 부족하면 공간을 2배로 할당한다.
st.Start();
for( int i = 0; i < 300000; i++)
{
sb.Append("1");
}
st.Stop();
Console.WriteLine("StringBuilder로 작업하는데 걸린 시간 : " + st.Elapsed); //2밀리초 정도 걸린다.
}
}
Text Encoding
Text.Encoding에선 인코딩 타입을 제공해준다. ASCII, Default, Unicode(UTF16), UTF32, UTF8 등이 주로 쓰인다.
class TextEncodingEx
{
public static void Main()
{
const string fileName = "UTF8Test.txt";
byte[] dataArray = null;
using (System.IO.FileStream
fileStream = new FileStream(fileName, FileMode.Open))
{
dataArray = new byte[fileStream.Length];
fileStream.Read(dataArray, 0, dataArray.Length);
string data = Encoding.UTF8.GetString(dataArray);
//UTF8로 생성된 txt파일을 정상적으로 읽어온다. BOM문자도 읽어와서 ?로 뜬다.
Console.WriteLine(data);
}
}
}
정규 표현식
정규 표현식 표기법은 여기 를 참고한다.
class RegularExpressionEx //정규 표현식을 위한 클래스이다.
{
public static void Main()
{
string email = "black개dog14@gmail.com";
string email2 = "blackdog14@★gmail.com";
Console.WriteLine(IsEmail(email)); //true
Console.WriteLine(IsEmail(email2)); //false
Console.WriteLine(isEmail2(email)); //true;
Console.WriteLine(isEmail2(email2)); //false;
string txt = "Hello World! Welcome to my world!";
Regex regex = new Regex("world", RegexOptions.IgnoreCase); //ignoreCase옵션을 넣어주면 대소문자 구분 없이 비교한다.
string result = regex.Replace(txt, "Universe");
Console.WriteLine(txt); //원래 문자열 출력
Console.WriteLine(result); //world가 Universe로 대체된 문자열 출력
}
//내가 생각한 이메일 규칙은 1. @문자를 반드시 한 번 포함한다.
//2. @ 이전의 문자열은 문자와 숫자만 허용한다.
//3. @ 이후의 문자열은 문자와 숫자만 허용하고 반드시 1번의 .문자를 포함한다.
public static bool IsEmail(string txt)
{
string[] parts = txt.Split('@');
if (parts.Length != 2) //@ 문자를 0번 포함하거나 2번 이상 포함하면 false 리턴
{
return false;
}
if (!isAlphaNumeric(parts[0])) //@ 문자 앞의 문자열이 문자나 숫자가 아니면 false 리턴
{
return false;
}
parts = parts[1].Split('.');
if (parts.Length != 2) //. 문자를 0번 포함하거나 2번 이상 포함하면 false 리턴
{
return false;
}
if( isAlphaNumeric(parts[0]) && isAlphaNumeric(parts[1])) //.문자 앞 뒤의 문자열들이 문자나 숫자가 아니면 false 리턴
{
return true;
}
else
{
return false;
}
}
public static bool isAlphaNumeric(string txt) //문자나 숫자인지 판별
{
foreach(char ch in txt)
{
if( !char.IsLetterOrDigit(ch) )
{
return false;
}
}
return true;
}
public static bool isEmail2(string txt) //정규식으로 표현
{
Regex regex = new Regex(@"([0-9a-zA-Z]+)@([0-9a-zA-Z]+)(\.[0-9a-zA-Z]+)$");
return regex.IsMatch(txt);
}
}
직렬화
BitConverter
문자열은 인코딩 방식에 따라 바이트 배열로의 전환이 달라질 수 있지만, 기본 자료형은 변환 방법이 고정되어 있다. BitConverter 타입의 GetByte 메서드를 이용하면 기본 자료형을 바이트 배열로 변환할 수 있다. 그리고 바이트 배열의 16진수 값을 Tostring 메서드를 통해 제공한다.
class SerialEx
{
public static void Main()
{
//기본 타입을 바이트 배열로 변환
byte[] boolBytes = BitConverter.GetBytes(true);
byte[] shortBytes = BitConverter.GetBytes((short)32000);
byte[] intBytes = BitConverter.GetBytes((int)16661151);
//바이트 배열을 다시 기본 타입으로 복원
bool boolResult = BitConverter.ToBoolean(boolBytes, 0);
short shortResult = BitConverter.ToInt16(shortBytes, 0);
int IntResult = BitConverter.ToInt32(intBytes, 0);
//복원 확인
Console.WriteLine(boolResult.ToString()); //true 출력
Console.WriteLine(shortResult.ToString()); //32000 출력
Console.WriteLine(IntResult.ToString()); // 16661151 출력
//바이트 배열을 16진수 문자열로 표현
//BitConverer 후, 출력 값이 엔디안 방식에 따라 cpu별로 다르게 표시 될 수 있다.
Console.WriteLine(BitConverter.ToString(boolBytes));
Console.WriteLine(BitConverter.ToString(shortBytes));
Console.WriteLine(BitConverter.ToString(intBytes));
int n = 1652300;
string text = n.ToString(); // int를 string으로 직렬화.
int result = int.Parse(text); // string을 int로 역직렬화.
Console.WriteLine(result); // 이 경우에는 직렬화 수단이 바이트 배열이 아닌 문자열이 된 것.
}
}
MemoryStream
메모리에 바이트를 순서대로 읽고 쓰는 작업을 수행하는 클래스다.
class MemoryStreamEx
{
public static void Main()
{
//직렬화
byte[] shortByte = BitConverter.GetBytes((short)32000); //short 32000를 바이트 배열로 직렬화
byte[] intByte = BitConverter.GetBytes(16522300); //int 16522300을 바이트 배열로 직렬화
MemoryStream ms = new MemoryStream(); //바이트 배열을 읽고 쓰기 위해서 MemoryStream 객체 생성
ms.Write(shortByte, 0, shortByte.Length); //ms 객체에 shortByte 기록 (2바이트)
ms.Write(intByte, 0, intByte.Length); //ms 객체에 intByte 기록 (4바이트)
ms.Position = 0; //ms에 순서대로 저장된 바이트 배열들을 다시 읽어오기 위해서 포지션을 0으로 설정
//역직렬화
byte[] outByte = new byte[2];
ms.Read(outByte, 0, 2); //outByte에는 ms의 0~1 사이에 있는 값들을 읽어온다. position은 2로 이동
short shortResult = BitConverter.ToInt16(outByte, 0); //바이트 배열을 short로 변환
Console.WriteLine(shortResult); //32000 출력
outByte = new byte[4];
ms.Read(outByte, 0, 4); //outByte에는 ms의 2~5 사이에 있는 값들을 읽어온다. position은 5로 이동
int intResult = BitConverter.ToInt32(outByte, 0); //바이트 배열을 int로 변환
Console.WriteLine(intResult); //16522300 출력
byte[] arr = ms.ToArray(); //ms를 바로 바이트 배열로 변환 가능.
short shortResult2 = BitConverter.ToInt16(arr, 0); //arr의 0번부터 변환
Console.WriteLine(shortResult2); //32000 출력
int intResult2 = BitConverter.ToInt32(arr, 2); //arr의 2번부터 변환
Console.WriteLine(intResult2); //16522300 출력
//Byte 배열에는 Position 기능이 없으므로 변환하려는 바이트의 위치를 직접 지정해줘야 한다.
}
}
StreamWriter/Reader
class StreamWREx
{
public static void Main()
{
//Stream에 문자열을 쓰려면 Encoding을 해서 바이트 배열로 변환해야 한다.
MemoryStream ms = new MemoryStream();
byte[] buf = Encoding.UTF8.GetBytes("Hello World!");
ms.Write(buf, 0, buf.Length);
//StreamWriter는 문자열 인코딩 방식을 생성자에서 받는다. 그 이후에는 바로 해당 인코딩 방식에 따라 자동으로 변환한다.
StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
StreamReader sr = new StreamReader(ms, Encoding.UTF8);
//읽을때는 저장 인코딩 형식을 맞춰야한다. 다른 인코딩 형식으로 읽으면 깨짐.
sw.WriteLine("안녕 세상아!");
//MemoryStream에 넣을 때와는 달리 직접 인코딩을 해주지 않아도 설정해둔 방식으로 들어간다.
Console.WriteLine(sr.ReadToEnd()); //아무것도 출력되지 않는다. 아직 내부버퍼에만 있는 상태여서.
sw.Flush(); //내부 버퍼에 보관하던 문자열들을 모두 stream에 쓴다. (기본 버퍼 사이즈 = 1024바이트)
Console.WriteLine(sr.ReadToEnd());
//아무것도 출력되지 않는다. 내부버퍼에서 stream으로 보냈지만, 포지션이 뒤쪽에 있어서 아무것도 없는곳을 읽는다.
ms.Position = 0;
Console.WriteLine(sr.ReadToEnd());
//내부버퍼에서 stream으로도 보냈고, 포지션도 0으로 초기화해서 stream안의 모든 문자열들이 출력된다.
sw.WriteLine("Blackdog");
sw.WriteLine(3000); //Tostring 메서드를 통해서 "3000"이 들어간다.
sw.Flush(); //내부 버퍼에 보관하던 문자열들을 모두 stream에 쓴다.
ms.Position = 0;
string txt = sr.ReadToEnd(); //안의 모든 내용을 읽기 위해서 포지션 0으로 초기화.
Console.WriteLine(txt);
}
}
BinaryWriter/Reader
class BinaryRWEx //기본형 타입을 2진 데이터로 바꿔서 쓴다. 문자열은 UTF-8로 바꾼다.
{
public static void Main()
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write("Hello World!" + Environment.NewLine); //문자열은 무조건 UTF-8인코딩
bw.Write("Blackdog" + Environment.NewLine);
bw.Write(1048575); //1111(15) + 1111 1111 1111 1111(65535) = 1048575
bw.Flush(); //내부 버퍼의 내용을 강제로 스트림에 쓴다. (내부 버퍼의 크기는 16바이트)
ms.Position = 0;
BinaryReader br = new BinaryReader(ms);
string first = br.ReadString();
string second = br.ReadString();
//int test = br.ReadInt32(); //int로 받아오면 1048575로 결과값이 출력된다.
ushort test = br.ReadUInt16(); //억지로 4바이트를 2바이트로 쪼개면 65535출력
ushort test2 = br.ReadUInt16(); //15출력. 순서가 뒤바뀐 이유는 리틀 엔디안이기 때문에.
Console.WriteLine("{0}{1}{2} {3}", first, second, test, test2); //Hello World!, Blackdog, 65535 15 출력.
byte[] arr = ms.ToArray();
Util.PrintArr(arr); //데이터의 의미 단위마다 그 길이를 알려주는 1바이트가 있고 그 뒤에 데이터가 바이트화 되서 출력된다.
//14 hello world! 개행, 10 blackdog 개행 식으로...
}
}
BinaryFormatter
BinaryFormatter는 2진 데이터로 직렬화하기 때문에 다른 사용자 정의 클래스 직렬화 방식에 비해서 속도가 빠르고 직렬화 결과의 용량도 작다. 그리고 닷넷 응용 프로그램끼리만 직렬화해서 데이터 교환이 가능하다.
[Serializable] //Serializable 특성을 지정하면 사용자 정의 클래스 직렬화를 간편하게 할 수 있다.
class Person
{
[NonSerialized] //Serializable 특성은 기본적으로 클래스 내 모든 프로퍼티를 대상으로 직렬화를 수행한다.
public int age; //직렬화를 원치 않는 프로퍼티는 NonSerialized 특성을 지정하면 된다..
public string name;
public Person(int age, string name)
{
this.age = age;
this.name = name;
}
public override string ToString()
{
return string.Format("{0} {1}", this.age, this.name);
}
}
class BinaryFormatterEx
{
public static void Main()
{
Person person = new Person(25, "Doh");
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, person);
ms.Position = 0;
Person clone = bf.Deserialize(ms) as Person;
Console.WriteLine(clone); //age에 NonSerialized를 지정하면 0으로 출력, 지정하지 않으면 입력한 그대로 25 출력.
byte[] arr = ms.ToArray(); //이 바이트 배열을 네트워크를 통해 다른 컴퓨터로 전송 후 다시 역직렬화 가능.
//다만 직렬화 방식이 닷넷 내부에 고유하게 정의되어서 다른 플랫폼에서는 역직렬화 불가.
Util.PrintArr(arr);
}
}
XmlSerializer
XmlSerializer는 클래스의 내용을 문자열로 직렬화한다. 그리고 [Serializable] 특성을 지정하지 않아도 사용할 수 있지만, 다음과 같은 제약사항이 있다.
- public 접근 제한자의 클래스여야 한다.
- 기본 생성자를 포함하고 있어야 한다.
- public 접근 제한자가 적용된 필드만 직렬화/역직렬화한다.
public class Person //public 접근 제한자의 클래스여야 XmlSerializer를 쓸 수 있다.
{
public int age; //public 으로 선언된 프로퍼티만 직렬화/역직렬화가 가능하다.
public string name;
private int gender;
public Person() //기본 생성자를 포함하고 있어야 한다.
{
}
public Person(int age, string name, int gender)
{
this.age = age;
this.name = name;
this.gender = gender;
}
public override string ToString()
{
return string.Format("{0} {1}", this.age, this.name);
}
}
class XmlSerializerEx
{
public static void Main()
{
MemoryStream ms = new MemoryStream();
XmlSerializer xs = new XmlSerializer(typeof(Person));
Person person = new Person(25, "Doh", 1);
//MemoryStream에 Person을 문자열로 직렬화.
xs.Serialize(ms, person); //public으로 선언한 age와 name만 직렬화된다.
ms.Position = 0;
Person clone = xs.Deserialize(ms) as Person;
Console.WriteLine(clone); //private으로 설정한 프로퍼티는 직렬화 되지 않아서 출력되지 않음.
byte[] buf = ms.ToArray();
Console.WriteLine(Encoding.UTF8.GetString(buf));
//Xml형식으로 된 문자열을 출력한다. 기본적으로 객체를 직렬화 할 때 UTF-8 인코딩 문자열로 직렬화한다.
//XmlSerializer는 다른 플랫폼 사이에서 상호 운용성이 높다.
//하지만 실제 전송되는 데이터에 비해 형식상 붙는 데이터가 많아서 크기가 커진다.
}
}
DataContractJsonSerializer
DataContractJsonSerializer는 클래스의 내용을 Json으로 직렬화한다. XmlSerializer보다 더 적은 크기로 변환이 가능하다. 또 닷넷 이외의 여러 플랫폼 사이에서도 변환이 가능하다.
[DataContract] //직렬화하려는 클래스에 표시. public으로 선언되어 있으면 꼭 표시해주지 않아도 된다.
class Person //직렬화 대상 클래스의 접근 제한에 영향을 받는다. 다른 클래스의 inner 클래스가되서 private되면 예외가 발생한다.
{ //inner 클래스가 됐어도 [DataContract],[DataMember] 특성을 정의해주면 직렬화가 가능하다.
//그 외 접근이 가능한 상황에서는 명시적으로 선언하지 않아도 작동한다.
[DataMember]
public int age;
[DataMember] //[DateContract]를 선언했을 때, [DataMember]를 선언하지 않으면 직렬화되지 않는다.
public string name;
[DataMember]
private int gender; //private은 직렬화되지 않는다.
public Person()
{
}
public Person(int age, string name, int gender)
{
this.age = age;
this.name = name;
this.gender = gender;
}
public override string ToString()
{
return string.Format("{0} {1}", this.age, this.name);
}
}
public class JsonSerializerEx
{
public static void Main()
{
DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(Person));
//참조관리자를 통해서 System.Runtime.Serialization 추가해야 사용가능.
MemoryStream ms = new MemoryStream();
Person person = new Person(25, "Doh", 1);
dcjs.WriteObject(ms, person);
ms.Position = 0;
Person clone = dcjs.ReadObject(ms) as Person;
Console.WriteLine(clone);
byte[] arr = ms.ToArray();
Console.WriteLine(Encoding.UTF8.GetString(arr));
//{"age":25,"gender":1,"name":"Doh"}를 출력한다. 기본적으로 UTF-8로 인코딩하기 때문에
//읽기 위해서 UTF-8로 인코딩한다.
}
}
컬렉션
배열은 크기가 고정되어 있다. 변수 자체에서는 재할당을 통해서 크기를 바꿀 수 있지만 크기를 바꾸기 전에 할당한 값은 보존되지 않는다. 컬렉션을 이용하면 크기가 바뀌면서 값이 보존되는 배열같은 기능을 사용할 수 있다.
ArrayList
class ArrayListEx
{
public static void Main()
{
ArrayList al = new ArrayList();
al.Add("Hello"); //박싱이 발생한다.
al.Add(6); //값 형식을 담으면 박싱이 발생하기 때문에 값 형식을 담기에는 적절치 않다. 대신 List<T>가 권장된다.
al.Add("World!");
al.Add(true);
int num = (int)al[1]; //언박싱
Console.WriteLine(al.Contains(6)); //6의 포함여부 검사. true.
Console.WriteLine(al.Contains("hello")); //대소문자 불일치 때문에 false.
al.Remove("World!"); //삭제하면 [3]에 있던 True가 자동으로 [2]로 온다.
Console.WriteLine(al[2]); //true.
al[2] = false;
Console.WriteLine(al[2]); //false;
Console.WriteLine("ArrayList내 모든 요소 출력");
foreach (object obj in al)
{
Console.WriteLine(obj);
}
}
}
IComparer 인터페이스를 이용한 정렬 구현 예제이다.
class BackNumberComparer : IComparer //IComparer를 구현하는 등번호 내림차순 정렬하는 클래스를 만든다.
{
public int Compare(object x, object y)
{
Person person1 = x as Person;
Person person2 = y as Person;
if (person1.BackNumber > person2.BackNumber) return -1;
else if (person1.BackNumber == person2.BackNumber) return 0;
else return 1;
}
}
class Person //Person의 속성은 등번호와 이름이다.
{
int backNumber;
string name;
public Person()
{
this.backNumber = 0;
this.name = "무명";
}
public Person(int backNumber, string name)
{
this.backNumber = backNumber;
this.name = name;
}
public int BackNumber { get => backNumber; set => backNumber = value; }
public string Name { get => name; set => name = value; }
}
class CollectionComparerEx
{
public static void Main()
{
ArrayList al = new ArrayList(); //ArrayList에 5개의 데이터를 입력한다.
al.Add(new Person(10, "강백호"));
al.Add(new Person(11, "서태웅"));
al.Add(new Person(4, "채치수"));
al.Add(new Person(14, "정대만"));
al.Add(new Person(7, "송태섭"));
al.Sort(new BackNumberComparer()); //BackNumberComparer를 인자로 전달한다.
foreach (Person person in al)
{
Console.WriteLine(person.BackNumber + "번 " + person.Name); //등번호 내림차순으로 정렬되어 나온다.
}
}
}
IComparable 인터페이스를 이용한 정렬 구현 예제이다.
class Person : IComparable //Person의 속성은 등번호와 이름이다.
{
int backNumber;
string name;
public Person()
{
this.backNumber = 0;
this.name = "무명";
}
public Person(int backNumber, string name)
{
this.backNumber = backNumber;
this.name = name;
}
public int BackNumber { get => backNumber; set => backNumber = value; }
public string Name { get => name; set => name = value; }
public int CompareTo(object obj) //CompareTo가 자동으로 호출되면서 비교작업을 수행한다.
{
Person person = obj as Person;
if (this.BackNumber > person.BackNumber) return -1;
else if (this.BackNumber == person.BackNumber) return 0;
else return 1;
}
public override string ToString()
{
return string.Format("{0}번 {1}", BackNumber, Name);
}
}
class CollectionIComparableEx
{
public static void Main()
{
ArrayList al = new ArrayList(); //ArrayList에 5개의 데이터를 입력한다.
al.Add(new Person(10, "강백호"));
al.Add(new Person(11, "서태웅"));
al.Add(new Person(4, "채치수"));
al.Add(new Person(14, "정대만"));
al.Add(new Person(7, "송태섭"));
al.Sort(); //안의 요소들이 IComparable을 구현하기 때문에 해당 요소의 CompareTo 메서드를 자동으로 호출해서 비교작업을 수행한다.
foreach (Person person in al)
{
Console.WriteLine(person); //등번호 내림차순으로 정렬되어 나온다.
}
}
}
Hashtable
Key값을 통해서 Value를 찾는다. ArrayList 같은 경우에 요소를 검색할 때 순차적으로 접근하지만, Hashtable은 Key값의 HashCode값을 통해서 바로 접근한다. 때문에 컬렉션의 크기가 클수록 Hashtable을 쓰는게 유리하다.
class HashtableEx
{
public static void Main()
{
Hashtable ht = new Hashtable();
ht.Add("서태웅", 11); //Hashtable에 값 추가, key, value모두 object 타입으로 다뤄지기 때문에 박싱 발생.
ht.Add("강백호", 10);
ht.Add("정대만", 14);
ht.Add("채치수", 4);
ht.Add("송태섭", 7);
//ht.Add("송태섭", 3); //key값이 겹치는 경우 ArgumentException 발생
Console.WriteLine(ht["정대만"]);
ht.Remove("채치수"); //"채치수"의 value 삭제
ht["강백호"] = 100; //"강백호" 의 value 변경
foreach (object key in ht.Keys)
{
Console.WriteLine("{0} => {1}", key, ht[key]);
}
}
}
SortedList
SortedList는 Hashtable과 마찬가지로 key, value로 저장하지만 key자체가 정렬되어 저장된다. key값이 정렬 순서에 영향을 미친다.
class SortedListEx
{
public static void Main()
{
SortedList sl = new SortedList();
sl.Add(10, "강백호"); //값을 넣을 때 마다 key값 기준으로 정렬해서 저장된다. 숫자 오름차순.
sl.Add(11, "서태웅");
sl.Add(7, "송태섭");
sl.Add(14, "정대만");
sl.Add(4, "채치수");
foreach( object key in sl.Keys)
{
Console.WriteLine("{0}번 {1}", key, sl[key]); //숫자 오름차순으로 결과를 출력한다.
}
}
}
Stack
class StackEx
{
public static void Main()
{
Stack st = new Stack();
st.Push(1); //인자를 object로 다루기 때문에 박싱 발생
st.Push(2);
st.Push(3);
st.Push("강백호");
st.Push(7);
int last = (int)st.Pop(); //7제거하면서 last로 저장. 언박싱
st.Push(5);
last = (int)st.Peek(); //5 제거하지 않으면서 last로 저장. 언박싱
object[] arr = st.ToArray();
Util.PrintObjectArr(arr); //5, 강백호, 3, 2, 1 순으로 출력.
st.Clear();
object[] arr2 = st.ToArray();
Util.PrintObjectArr(arr2); //clear() 해서 아무것도 없기 때문에 출력되는게 없다.
}
}
Queue
class QueueEx
{
public static void Main()
{
Queue q = new Queue();
q.Enqueue(1); //매개변수를 object로 받기 때문에 박싱 발생.
q.Enqueue(2);
q.Enqueue(3);
q.Enqueue(4);
int last = (int)q.Dequeue(); //언박싱
Console.WriteLine(last); //1 출력.
q.Enqueue(5);
Console.WriteLine();
int[] copyArr = new int[5];
q.CopyTo(copyArr, 0); //2,3,4,5 복사.
Util.PrintIntArr(copyArr); //2,3,4,5,0 출력됨.
Console.WriteLine(q.Contains("강백호")); //false 출력
last = (int)q.Peek(); //맨 앞인 2를 삭제하지 않고 last에 저장.
Console.WriteLine(last); //2 출력.
Console.WriteLine();
object[] arr = q.ToArray();
Util.PrintObjectArr(arr); //2,3,4,5 출력
q.Clear();
Console.WriteLine();
arr = q.ToArray();
Util.PrintObjectArr(arr); //clear되었기 때문에 아무것도 출력하지 않는다.
}
}
파일
FileStream
FileMode
열거형 값 | 설명 |
---|---|
CreateNew | 파일을 항상 새로 만든다. 같은 이름의 파일이 존재하면 IOException 예외가 발생 |
Create | 파일을 생성한다. 같은 이름의 파일이 존재하면 기존 데이터를 지우고 만든다. |
Open | 이미 있는 파일을 연다. 만약 열려는 파일이 존재하지 않으면 FileNotFoundException 발생 |
OepnOrCreate | 같은 이름의 파일이 있다면 열고, 아니면 새로 만든다. |
Truncate | 이미 있는 파일을 열고, 기존 데이터는 모두 삭제한다. 만약 열려는 파일이 존재하지 않으면 FileNotFoundException 발생 |
Append | 파일을 무조건 열어서 기존 데이터 내용 뒤로 덧붙인다. 파일이 존재하지 않으면 새로 만든다. |
FileAccess
열거형 값 | 설명 |
---|---|
Read | 파일을 읽기 목적으로 연다. |
Write | 파일을 쓰기 목적으로 연다. |
ReadWrite | 파일을 읽기 및 쓰기 목적으로 연다. FileAccess.Read l FileAccess.Write 로도 쓸 수 있다. |
FileShare
열거형 값 | 설명 |
---|---|
None | 같은 파일을 두 번 이상 열면 실패한다. 맨 처음 파일을 열고 있는 FileStream만 사용 가능케 하는 설정이다. |
Read | 같은 파일에 대해 Read로 여는 것만 허용한다. 맨 처음 파일을 열고 있는 FileStream만 모든 동작이 가능하고 그 다음부터 적용한다. |
Write | 같은 파일에 대해 Write로 여는 것만 허용한다. 맨 처음 파일을 열고 있는 FileStream만 모든 동작이 가능하고 그 다음부터 적용한다. |
ReadWrite | 같은 파일에 대해 Read, Write로 여는 것 모두 허용한다. 같은 파일에 대해 서로 다른 FileStream에서 읽고 쓰는 것이 가능하다. |
class FileStreamEx
{
public static void Main()
{
using (FileStream fs = new FileStream("test.log", FileMode.OpenOrCreate))//test.log라는 파일을 없으면 생성하고 있으면 연다.
{
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
sw.WriteLine("Hello World!");
sw.WriteLine("Blackdog!");
sw.WriteLine(32000);
sw.Flush();
}
using (FileStream fs = new FileStream("test2.log", FileMode.Append, FileAccess.Write ,FileShare.Read))
//test2.log라는 파일을 없으면 만들고 있으면 기존 내용에 덧붙인다.
//실행 횟수만큼 반복해서 뒤에 덧붙여진다.
//단어의 길이를 나타내는 바이트가 앞에 덧붙여지기 때문에 메모장으로 열면 그 바이트도 문자로 인식되어 메모장에서 보인다.
{
BinaryWriter bw = new BinaryWriter(fs, Encoding.UTF8);
bw.Write("Hello World!" + Environment.NewLine);
bw.Write("Blackdog" + Environment.NewLine);
bw.Write(32000); //32000도 2진 데이터로 변환되었다가 문자열로 취급당해서 } 로 출력된다.
bw.Flush();
//using (FileStream fs2 = new FileStream("test2.log", FileMode.Append, FileAccess.Write))
////FileShare.None으로 설정된 블록 안에서 다시 그 파일을 접근하려 하면
////파일은 다른 프로세스에서 사용 중이므로 프로세스에서 액세스할 수 없습니다. 라는 System.IOException 예외 출력.
//{
//}
Console.ReadLine();
//프로세스 중에 윈도우에 열어보기 위해 ReadLine으로 잠시 프로그램이 종료되는 것을 막는다.
//윈도우에서 접근 시, 다른 프로세스가 파일을 사용 중이기 때문에 프로세스가 액세스 할 수 없습니다. 라는 메시지가 출력된다.
//메모장에서 여는 건 Read이기 때문에 막힌다. FileShare.Write로 설정해도 역시 막힌다.
//FileShare.Read로 설정하고 메모장으로 열면 잘 열린다.
}
using (FileStream fs2 = new FileStream("test2.log", FileMode.Open, FileAccess.Write))
{
BinaryWriter bw = new BinaryWriter(fs2, Encoding.UTF8);
bw.Write("adf adfadf!" + Environment.NewLine); //open으로 했으므로 이 내용이 메모장 제일 앞에 덮어서 기록됨.
bw.Flush();
}
//using (FileStream fs2 = new FileStream("test2.log", FileMode.Append, FileAccess.ReadWrite))
////Append는 쓰기 전용 모드에서만 사용 가능.
////Argument Exception 발생
//{
//}
}
}
class Program //다른 프로젝트에서 접근했을 때의 코드
{
static void Main(string[] args)
{
Console.WriteLine(Environment.CurrentDirectory);
Environment.CurrentDirectory = @"C:\Users\DH\Documents\Visual Studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug";
Console.WriteLine(Environment.CurrentDirectory);
//존재하지 않는 디렉토리가 경로상에 있으면 DirectoryNotFoundException 예외 발생
using (FileStream fs3 = new FileStream(Environment.CurrentDirectory + "\\test2.log", FileMode.Open, FileAccess.ReadWrite))
{
BinaryWriter bw = new BinaryWriter(fs3, Encoding.UTF8);
bw.Write("??" + Environment.NewLine);//프로세스 중에는 다른 프로세스에서 열리지 않는다.
bw.Flush();
}
}
}
File/FileInfo
class FileEx
{
public static void Main()
{
File.Copy("복사전.txt", "복사후.txt"); //같은 폴더에 있는 복사전.txt를 복사후.txt로 복사한다.
//File.Copy("복사전.txt", "복사후.txt", true); //같은 폴더의 복사전.txt를 복사후.txt로 덮어쓴다.
//복사후.txt 파일이 있으면 System.IO.IOException: '복사후.txt' 파일이 이미 있습니다. 예외 출력
Console.WriteLine(File.Exists("복사전.txt")); //폴더 내에 복사전.txt가 존재하므르 true 없으면 false.
File.Move("복사후.txt", "..\\이동후.txt"); //상위 폴더에 이동후.txt라는 이름으로 바뀌어서 이동한다.
File.Move("복사후.txt", "이동후.txt"); //폴더가 동일하면 파일명 변경.
string target = "타겟.txt"; //Move 메서드는 덮어쓰기 같은 옵션이 없으므로 다음과 같이 덮어쓰기를 구현 가능.
if (File.Exists(target) == true)
{
File.Delete(target);
}
File.Move("전타겟.txt", target);
//이미 그 경로에 있으면, System.IO.IOException: 파일이 이미 있으므로 만들 수 없습니다. 예외 출력.
byte[] arr = File.ReadAllBytes("복사전.txt");
Util.PrintByteArr(arr); //복사전.txt를 읽어서 byte 배열로 출력.
Console.WriteLine(Encoding.UTF8.GetString(arr)); //test라는 내용 출력.
string[] txtArr = File.ReadAllLines("ReadAllLinesTest.txt", Encoding.Default); //줄마다 string 하나씩으로 배열에 들어간다.
foreach (string txtLine in txtArr) Console.WriteLine(txtLine); //모두 출력.
string txt = File.ReadAllText("ReadAllLinesTest.txt", Encoding.Default); //모든 텍스트가 string 하나에 들어간다.
Console.WriteLine(txt);
File.WriteAllBytes("바이트배열복사후.txt", arr); //그대로 text로 복사됨.
File.WriteAllLines("WriteAllLineTest.txt", txtArr); //1줄 2줄 3줄 4줄 5줄 내용 모두 복사 됨.
File.WriteAllText("WriteAllTextTest.txt", txt); //1줄 2줄 3줄 4줄 5줄 내용 모두 복사 됨.
string c20Text = new string('c', 20); //c x 20개의 문자열 생성
File.WriteAllText("씨이십개.txt", c20Text);
string clone = File.ReadAllText("씨이십개.txt");
Console.WriteLine(clone);
//FileInfo 타입은 File 타입의 기능을 인스턴스 멤버로 일부 구현함.용법은 같다.
//FileInfo source = new FileInfo("소스.txt"); 로 소스 설정하고 사용하면 된다.
}
}
Directory/DirectoryInfo
Directory와 DirectoryInfo의 관계도 File과 FileInfo의 관계와 동일하다.
class DirectoryEx
{
public static void Main()
{
Directory.CreateDirectory("만들어볼까!");
//이미 경로에 똑같은 이름의 디렉토리가 존재하면 아무것도 안한다.
//Directory.CreateDirectory("만들어볼까?");
//'?' 때문에 System.ArgumentException: 경로에 잘못된 문자가 있습니다. 예외 출력.
//Directory.CreateDirectory("지워볼까!");
//경로에 존재하지 않는 디렉터리를 삭제할 경우 DirectoryNotFoundException 예외 출력.
Console.WriteLine(Directory.Exists("진짜있어!")); //true 출력.
string[] paths = Directory.GetDirectories("/");
//경로의 형식이 잘못된 경우 System.ArgumentException 예외 출력.
foreach (string path in paths) Console.WriteLine(path); //경로 내의 디렉터리 목록을 출력한다.
Console.WriteLine();
string[] files = Directory.GetFiles("/");
foreach (string file in files) Console.WriteLine(file); //경로 내의 파일 목록을 출력한다.
foreach (string txt in Directory.GetLogicalDrives()) Console.WriteLine(txt); //시스템에 설치된 디스크 드라이브의 문자 목록 출력.
//특정 폴더와 그 하위 폴더를 검색해서 파일 찾기
string targetPath = @"C:\Users\DH\Documents\Visual Studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug";
foreach (string txt in Directory.GetFiles(targetPath, "*.???", SearchOption.AllDirectories)) Console.WriteLine(txt);
//검색하려는 파일명, 확장자와 맨 위 디렉토리만 검색할지, 그 하위도 검색할지 설정가능.
}
}