< 타입 안정성이란? > 

타입 안정성은 간단히 말해서 변수나 객체가 예상한 데이터 타입과 일치 하는 값을 가지도록 하는 것을 의미한다.

예를 들어, 문자열을 숫자에 할당하려고 시도하면 컴파일러는 이를 에러로 간주한다.

 

< 왜 타입 안정성이 중요한가? > 

타입 안정성은 코드의 실수를 미리 잡아낼 수 있게 해준다. 이를 통해 런타임 에러의 가능성을 크게 줄일 수 있다. 

 

< 예제 >

타입 안정성이 없는 경우

ViewBag.Username = "Alice";

런타임 시에만 오타나 문제를 발견할 수 있다.

 

타입 안정성을 가진 경우

var model = new UserViewModel
{
    Username = "Alice"
};

컴파일 단계에서 문제를 미리 찾아낼 수 있다.

 

 

< 디컴파일의 개념 >

먼저, 디컴파일을 이해하기 위해서는 컴파일에 대해 알아야 한다. 컴파일이란, 개발자가 작성한 소스 코드(고급언어)를 컴퓨터가 이해할 수 있는 기계어(저급언어)로 변환하는 과정을 의미한다.

그렇다면 디컴파일은 무엇일까? 말 그대로 '디컴파일'은 컴파일의 반대 과정이다. 즉, 컴퓨터가 이해하는 기계어를 다시 사람이 이해할 수 있는 고급언어로 변환하는 과정을 말한다.

< 왜 디컴파일이 필요한가? >

그럼 왜 이런 과정이 필요한 것일까? 주로 다음의 경우에 디컴파일이 사용된다.

소스 코드를 잃어버린 경우: 원본 소스 코드가 없더라도 컴파일된 실행 파일로부터 소스 코드를 복구할 수 있다.

소프트웨어 리버스 엔지니어링: 기계어로 변환된 프로그램을 분석해 그 작동 원리를 파악하거나 보안 취약점을 찾는데 사용된다.

< 실습 >

실습에 앞서, EXE파일,DLL파일의 실 소유자의 동의가 필요하기에 라이센스의 디컴파일 금지조항을 반드시 확인해야 한다. 아니면 불법 복제 및 소스코드 무단 도용 시, 법적 소송이 발생할 수 있기에 반드시 동의를 구하고 디컴파일 해야한다.

해당 실습에서는 코드를 블러 처리 한다.

 

우선 새로운 프로젝트를 하나 만들도록 한다. 

참조 추가를 해서, 원하는 exe,DLL파일을 추가한다.

찾아보기 탭에서 원하는 exe,dll파일을 가져온다음, 확인을 누른다.

이후, 도구 -> 옵션 -> 텍스트 편집기 -> 고급 탭에서  디 컴파일된 원본으로의 이동을 사용하도록 설정을 체크 해준다.

기존 Form1에서 해당 디컴파일된 코드이름을 불러와 정의로 이동을 한다. 예 : 디컴파일된 이름이 Hello이면

.하면 여러 클래스가 보일 것인데, 거기서 특정 불러오고 싶은 클래스를 가져온 다음, 정의로 이동을 하면 된다. 

정의로 이동을 할 때, 아래와 같이 창이 뜬다면 동의를 받고 확인을 누르자.

그럼 참조 추가한 파일의 원본 코드가 보일 것이다. 다만 cs파일의 소스코드와 디자인파일의 소스코드가 혼합되어 있을 수 있기에 분리작업을 통하여 코드를 완성해내면 된다.

 

이후 실행을 하면, exe파일의 원본 실행을 알 수 있다.

 

[Flags]는 특별한 목적을 위해 사용되는 특성(attribute)이다. 주로 enum 형식을 비트 연산을 위한 플래그 집합으로 사용할 때 붙인다. 이를 통해 여러 가지 상태를 한 변수에 저장하고, 비트 단위의 연산을 수행할 수 있다.

< [Flags]의 정의와 사용 시기 >

[Flags] 속성은 enum(열거형)에 대해 선언되며, 각각의 값이 비트 수준에서 조합될 수 있음을 나타낸다. 이는 주로 '플래그(flag)' 역할을 하는 enum을 정의할 때 사용된다. 이렇게 선언된 enum 값은 비트 OR 연산자(|)를 이용해 합쳐지거나, 비트 AND 연산자(&)를 통해 특정 플래그가 활성화되었는지 확인할 수 있다.

[Flags]
public enum MyFlags
{
    None = 0,   // 0000
    Flag1 = 1,  // 0001
    Flag2 = 2,  // 0010
    Flag3 = 4,  // 0100
    Flag4 = 8   // 1000
}

 

각 플래그는 2의 거듭제곱을 사용하므로, 각 플래그가 고유한 비트를 차지한다. 이렇게 설정함으로써 각 플래그를 조합하여 사용할 수 있다.
[[Flags]의 사용 예]

MyFlags activeFlags = MyFlags.Flag1 | MyFlags.Flag3;

// Flag1 and Flag3 are active
Console.WriteLine(activeFlags.HasFlag(MyFlags.Flag1)); // prints True
Console.WriteLine(activeFlags.HasFlag(MyFlags.Flag3)); // prints True

// Flag2 and Flag4 are not active
Console.WriteLine(activeFlags.HasFlag(MyFlags.Flag2)); // prints False
Console.WriteLine(activeFlags.HasFlag(MyFlags.Flag4)); // prints False

 

위 코드에서는 MyFlags.Flag1와 MyFlags.Flag3를 OR 연산을 통해 activeFlags에 저장한다. 이후 HasFlag 메소드를 통해 각 플래그가 활성화되었는지를 확인할 수 있다.

< [Flags] 사용 시 주의사항 >

[Flags]를 사용할 때는 두 가지 주요한 주의사항이 있다.
플래그의 값이 2의 거듭제곱이어야 한다. 이렇게 설정해야 각 플래그가 고유한 비트를 차지할 수 있다.
None 값은 항상 0을 사용해야 한다. 이는 아무런 플래그도 설정되지 않았음을 나타내는 값으로, 해당 플래그를 모두 클리어하는데 사용된다.

프로그래밍에서 데이터 보호는 중요한 이슈 중 하나다. 그래서 다양한 데이터 접근 제어 방법을 사용하여 데이터를 보호한다. C#에서도 이를 위해 접근 제어자(access modifiers)를 제공하고 있다.  특히 'public'과 'protected' 속성 그리고 'private' 멤버에 대해 깊게 살펴볼 것이다.

< 데이터 접근 제어란? >

데이터 접근 제어는 클래스의 멤버에 대한 접근을 제어하는 방식을 의미한다. 멤버는 필드, 메소드, 프로퍼티 등을 포함한다. C#에서는 접근 제어자를 사용해 이를 수행할 수 있으며, 가장 일반적으로 사용되는 접근 제어자로는 public, private, protected, internal 등이 있다.

[Public과 Protected 속성]

C#에서 속성(Property)은 객체의 상태를 표현하는 데 사용된다. 이를 통해 객체의 내부 상태를 안전하게 읽고 수정할 수 있다. 속성은 필드처럼 보이지만, 내부적으로는 메서드인 Get과 Set을 통해 동작한다.

Public 속성은 클래스의 외부에서 자유롭게 접근할 수 있도록 하는 방식이다. 즉, 클래스가 속한 어셈블리나 다른 어셈블리에서도 이 속성에 접근할 수 있다.

public class MyClass {
    public string MyProperty { get; set; }
}

 

반면에, Protected 속성은 해당 클래스와 해당 클래스를 상속받은 클래스에서만 접근 가능하게 한다. 그래서 다른 클래스에서는 직접적인 접근이 불가능하다.

public class MyBaseClass {
    protected string MyProperty { get; set; }
}

 

Public과 Protected 속성은 클래스의 사용자에게 데이터에 직접적으로 접근할 수 있는 방법을 제공한다. 하지만 이는 데이터의 상태를 제어하는 측면에서 위험성을 내포하고 있다.

< 모든 데이터 멤버는 Private으로 >

이런 위험성을 피하기 위해, 클래스의 모든 데이터 멤버는 가능한 Private으로 선언하는 것이 바람직하다. Private 멤버는 오직 그 멤버가 선언된 클래스에서만 접근 가능하며, 외부에서는 접근할 수 없다.

public class MyClass {
    private int myField;
}

 

이 방법은 데이터 캡슐화를 강화하며, 클래스 내부의 상태를 외부로부터 보호하는데 중요한 역할을 한다. 이는 클래스를 수정하거나 확장할 때도 이점을 준다. 외부에 노출된 부분이 적을수록 클래스의 구현을 수정하거나 확장하기 쉽기 때문이다.

< 인덱서(Indexer)의 사용 >

마지막으로, C#에서는 배열이나 컬렉션과 같은 시퀀스나 딕셔너리에 접근할 때 인덱서(Indexer)를 사용하는 것이 좋다. 인덱서는 클래스나 구조체의 인스턴스를 배열처럼 접근할 수 있게 해주는 멤버다.

public class MyClass {
    private List<string> myList = new List<string>();

    public string this[int index] {
        get { return myList[index]; }
        set { myList[index] = value; }
    }
}

 

이렇게 하면, 클래스의 사용자는 내부 데이터를 배열처럼 다룰 수 있게 된다. 하지만 실제로 데이터가 어떻게 저장되고 접근되는지는 완전히 감추어진다. 이로 인해 데이터의 캡슐화가 더욱 강화된다.


이렇게 public이나 protected로 데이터를 노출할 때는 항상 속성을 사용하고, 시퀀스나 딕셔너리를 노출할 때는 인덱서를 사용하며, 모든 데이터 멤버는 private으로 선언하게 되면 클래스의 캡슐화를 강화하고 더욱 안전하고 유지보수하기 쉬운 코드를 작성할 수 있다.


 

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

디컴파일  (0) 2023.07.12
[Flags]  (0) 2023.06.29
C# Indexers 적용/활용 방법  (0) 2023.06.29
자동속성(Auto-Implemented Properties)/가상속성(Virtual Properties)  (0) 2023.06.28
데이터 바인딩  (0) 2023.06.28

< 인덱서에서의 유효성 검증과 계산>

인덱서를 사용하면 내부 데이터에 직접 접근하는 것이 아니라 메소드를 통해 접근하므로 유효성 검사나 추가적인 계산을 수행할 수 있다. 예를 들어, 내부 배열에 접근하는 인덱서에 유효한 인덱스만 허용하도록 유효성 검사를 추가할 수 있다.

public T this[int i]
{
    get
    {
        if (i >= 0 && i < arr.Length)
        {
            return arr[i];
        }
        else
        {
            throw new IndexOutOfRangeException();
        }
    }
    set
    {
        if (i >= 0 && i < arr.Length)
        {
            arr[i] = value;
        }
        else
        {
            throw new IndexOutOfRangeException();
        }
    }
}

 

이 코드는 인덱스가 배열의 범위를 벗어나지 않는지 확인하고, 그렇지 않은 경우 IndexOutOfRangeException을 발생시킨다.

< 인덱서와 상속 >

인덱서는 virtual, abstract 또는 override 키워드와 함께 사용될 수 있다. 이를 통해 인덱서의 동작을 하위 클래스에서 변경하거나 확장할 수 있다. 예를 들어, 기본 클래스에서 virtual 키워드를 사용해 인덱서를 정의하고, 하위 클래스에서 override 키워드를 사용해 그 동작을 변경할 수 있다.

public virtual T this[int i]
{
    get { /* 기본 구현 */ }
    set { /* 기본 구현 */ }
}

// 하위 클래스에서
public override T this[int i]
{
    get { /* 변경된 구현 */ }
    set { /* 변경된 구현 */ }
}

 

이를 통해 클래스 계층에서 인덱서의 동작을 더욱 유연하게 관리할 수 있다.

< 인터페이스와 인덱서 >

인덱서는 인터페이스에도 포함될 수 있다. 이를 통해 특정 클래스가 반드시 구현해야 하는 인덱서를 지정할 수 있다.

public interface ISampleCollection<T>
{
    T this[int i] { get; set; }
}
// 이 인터페이스를 구현하는 클래스에서
public class SampleCollection<T> : ISampleCollection<T>
{
    // 인터페이스의 인덱서 구현
    public T this[int i]
    {
        get { /* 구현 */ }
        set { /* 구현 */ }
    }
}


이를 통해 클래스 간의 계약을 정의하고 코드의 일관성을 유지할 수 있다.

< 읽기 전용 또는 읽기/쓰기용 인덱서 >

인덱서는 get 접근자만을 포함하여 읽기 전용으로 만들 수 있다. 반대로 get과 set 접근자를 모두 포함하여 읽기/쓰기용으로 만들 수도 있다. 이를 통해 클래스의 내부 데이터에 대한 접근을 더욱 세밀하게 제어할 수 있다.

// 읽기 전용 인덱서
public T this[int i]
{
    get { /* 구현 */ }
}

// 읽기/쓰기용 인덱서
public T this[int i]
{
    get { /* 구현 */ }
    set { /* 구현 */ }
}


이를 통해 클래스의 내부 데이터를 보호하고 외부에서의 변경을 제어할 수 있다.

< 데이터 바인딩과 인덱서 >

정수를 매개변수로 받는 1차원 인덱서는 데이터 바인딩에 유용하게 사용될 수 있다. 데이터 바인딩이란 데이터를 UI 요소에 연결하는 프로세스를 의미한다. 이런 방식으로, 인덱서를 통해 각 항목에 접근하면서 데이터를 UI 요소에 연결할 수 있다.

< 맵 정의와 인덱서 >

또한, 정수 이외의 매개 변수를 받는 인덱서는 맵(Map)을 정의하는데 유용하다. 키-값 쌍을 저장하는 자료구조인 맵에 접근하기 위해 인덱서를 사용할 수 있다. 예를 들어, 문자열 키와 값을 갖는 맵을 정의하려면 다음과 같이 인덱서를 사용할 수 있다.

public T this[string key]
{
    get { /* key에 해당하는 값을 반환 */ }
    set { /* key에 해당하는 값을 설정 */ }
}

 

이렇게 해서 인덱서는 클래스가 배열처럼 동작하게 하고, 동시에 캡슐화를 유지하면서 복잡한 동작을 가능하게 할 수 있다.


< 자동속성(Auto-Implemented Properties) >

클래스 내에 데이터 필드를 선언하고 이를 접근하기 위한 메서드를 작성하는 것은 매우 일반적인 일이다. 이렇게 필드에 대한 접근 제어를 하기 위해 getter와 setter를 사용한다. 이렇게 필드와 이를 제어하는 메서드를 함께 묶어 속성(Property)이라 부른다.

public class Coffee
{
    private string _name;  // 필드

    public string Name    // 속성
    {
        get { return _name; }  // getter
        set { _name = value; } // setter
    }
}

 

그러나 이와 같은 방식은 코드의 중복성을 증가시키고 가독성을 낮추는 문제가 있다. 그래서 이를 해결하기 위해 자동속성(Auto-Implemented Properties)이라는 개념을 도입했다.

public class Coffee
{
    public string Name { get; set; }
}

 

자동속성을 사용하면 필드는 컴파일러에 의해 자동으로 생성되며, getter와 setter 메서드도 자동으로 생성된다. 이는 코드의 중복성을 줄이고 가독성을 높여 준다.

자동속성은 특히 데이터 전송 객체(Data Transfer Object, DTO)나 데이터 모델 클래스 등의 작성에 주로 사용된다. 이들 클래스는 데이터를 저장하고 전달하는 역할을 하는데, 이때 개별 필드에 대해 별도의 로직 없이 단순히 데이터를 저장하거나 반환하는 작업이 주로 이루어지기 때문에 자동속성을 사용하면 매우 효율적이다.

[주의사항]
자동속성은 필드에 대해 별도의 로직이 없을 때 사용하는 것이 좋다. 필드의 값을 가져오거나 설정하는 과정에서 별도의 검증 로직이나 계산이 필요한 경우에는 일반 속성을 사용하는 것이 좋다.
자동속성은 기본적으로 public으로 선언되지만, 필요에 따라 접근 제어자를 변경할 수 있다. 하지만 get과 set에 대해 별도의 접근 제어자를 지정할 수도 있으니 이 점을 주의해야 한다. 예를 들어, 다음과 같이 속성을 선언할 수 있다.

public class Coffee
{
    public string Name { get; private set; }  // 외부에서는 읽기만 가능하고, 클래스 내에서만 값 설정 가능
}

 

< 가상속성(Virtual Properties) >

가상속성은 상속받은 클래스에서 재정의(Override)할 수 있는 속성이다. 이는 다형성(Polymorphism)의 한 형태로, 같은 이름의 속성이지만 각각의 클래스에서 다른 동작을 수행하도록 하는 것이 가능해진다.

public class Vehicle
{
    public virtual string Name { get; set; } = "Vehicle";
}

public class Car : Vehicle
{
    public override string Name { get; set; } = "Car";
}

 

이 경우, Vehicle 객체의 Name 속성은 "Vehicle"을 반환하고, Car 객체의 Name 속성은 "Car"을 반환한다. 가상속성은 이와 같이 상위 클래스에서 정의한 속성을 하위 클래스에서 필요에 따라 다르게 동작하도록 할 때 사용한다.

[주의사항]
하위 클래스에서 속성을 재정의할 때는 반드시 override 키워드를 사용해야 한다.
virtual 키워드를 사용하면 해당 속성이나 메서드는 재정의될 수 있음을 의미한다. 따라서 virtual 키워드를 사용할 때는 하위 클래스에서 이를 재정의할 가능성을 염두에 두고 설계해야 한다.
virtual 키워드를 사용할 경우, 실행 시간에 적절한 메서드나 속성이 호출되기 때문에 약간의 성능 손실이 발생할 수 있다. 따라서 성능이 중요한 상황에서는 이를 고려해야 한다.

차의 운전석에 앉아, 차를 운전할 때, 만약 핸들을 돌린 방향과 차량이 반대 방향으로 움직인다면 어떻게 될까? 위험하고 혼란스러울 것이다. 이처럼, 행동과 그 결과가 일관되게 반응하는 것이 중요한데
이와 비슷하게, 사용자 인터페이스(UI)를 다룰 때 우리는 UI 요소와 데이터 간의 일관성을 유지하려고 노력한다. 여기서 C#의 DataBindings 속성이 활용되는데, 이것은 UI 컴포넌트와 데이터 소스를 "연결"해주는 역할을 한다.

< 데이터 바인딩이란 무엇인가? >

"데이터 바인딩(Data Binding)"이란 이름에서 알 수 있듯이, 데이터와 어떤 요소를 '연결'하는 것이다. 사용자 인터페이스 요소 (예: 텍스트 박스)와 데이터 소스(예: 객체의 속성) 간에 '연결고리'를 만들어준다. 이로 인해 데이터가 변경될 때 UI가 자동으로 업데이트되고, 반대로 UI에서 사용자가 변경을 가하면 데이터 소스에도 반영된다.

왜 데이터 바인딩을 사용할까?

[일관성 유지]
데이터 바인딩의 가장 큰 장점 중 하나는 UI와 데이터 사이의 일관성을 유지할 수 있다는 것이다. 예를 들어, 데이터의 값이 변경되면 UI에 해당 변경이 자동으로 반영되어 사용자가 최신 상태를 보게 된다.

[코드 양 감소]
데이터 바인딩을 사용하면 UI 업데이트를 위한 코드를 수동으로 작성할 필요가 없어진다. 데이터의 변경을 감지하고 UI를 적절하게 업데이트하는 것을 프레임워크가 알아서 처리해준다.

[유지보수 용이]
데이터 바인딩은 모델과 뷰 간의 강력한 결합을 준다. 따라서 코드의 유지보수가 더 쉬워지고, 각 부분을 독립적으로 테스트하거나 변경하는 것이 용이해진다.



언제 데이터 바인딩을 사용하는가?


UI에서 표시하는 데이터가 자주 변경되거나, 사용자의 입력을 통해 데이터가 변경되는 경우 데이터 바인딩을 사용하면 매우 효율적이다. 또한, 복잡한 사용자 인터페이스를 가지고 있고 그 인터페이스가 다양한 데이터 소스와 상호작용하는 경우에도 유용하다.

textBoxCity.DataBindings.Add("Text", address, nameof(City));

 

여기서 textBoxCity는 사용자가 도시 이름을 입력할 수 있는 텍스트 박스다. address는 주소 정보를 담고 있는 객체이고, City는 그 중 도시 이름을 나타내는 속성이다.

이 코드를 통해 텍스트 박스의 'Text' 속성과 address 객체의 'City' 속성이 연결된다. 이제 사용자가 텍스트 박스에 도시 이름을 입력하면 address 객체의 'City' 속성이 자동으로 업데이트된다. 반대로, address 객체의 'City' 속성이 프로그램에 의해 변경되면 그 변경 사항이 텍스트 박스에 자동으로 반영된다.

 

 

Address.cs를 아래와 같이 만들고 

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

namespace WindowsFormsApp2
{
    public class Address : INotifyPropertyChanged
    {
        private string city;

        public event PropertyChangedEventHandler PropertyChanged;

        public string City
        {
            get { return city; }
            set
            
            {
                if (city != value)
                {
                    city = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(City)));
                }
            }
        }
    }
}

아래와 같이 Form1 코드를 작성한다 여기서 TextBox는 도구 툴에서 만들어도 된다. 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        private Address address;

        // textBoxCity를 선언
        private TextBox textBoxCity = new TextBox();

        public Form1()
        {
            InitializeComponent();

            // Address 객체를 생성하고 초기 도시를 '서울'로 설정
            address = new Address() { City = "서울" };

            // textBoxCity의 Text 속성을 address 객체의 City 속성에 바인딩
            textBoxCity.DataBindings.Add("Text", address, nameof(Address.City), false, DataSourceUpdateMode.OnPropertyChanged);

            // textBoxCity를 폼에 추가 가능 .
            this.Controls.Add(textBoxCity);
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
   
}

초기값 서울
여기선 울산인데 울산1로 변경해보았다.

 

setValue값이 바인딩 된 값이 즉시 반영되는 것을 확인할 수 있다.

Dictionary는 키와 값 쌍을 저장하는 컬렉션이다. 다른 언어에서는 '해시맵', '해시 테이블', '맵' 등으로 불리는 자료구조와 같다. Dictionary는 키를 사용하여 빠르게 데이터를 검색할 수 있도록 설계되어 있다.

Dictionary<TKey, TValue>는 두 개의 타입 매개변수를 사용한다

TKey: 딕셔너리의 키의 타입 지정

TValue: 딕셔너리의 값의 타입을 지정

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 정수 키와 문자열 값을 저장하는 Dictionary 생성
        Dictionary<int, string> dictionary = new Dictionary<int, string>();

        // 키-값 쌍 추가
        dictionary.Add(1, "One");
        dictionary.Add(2, "Two");
        dictionary.Add(3, "Three");

        // 키를 사용하여 값을 검색
        string value = dictionary[2];
        Console.WriteLine(value); // 출력: Two

        // 키-값 쌍 제거
        dictionary.Remove(1);

        // Dictionary의 모든 키-값 쌍을 반복 처리
        foreach (var pair in dictionary)
        {
            Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
        }

        // 키가 Dictionary에 존재하는지 확인
        if (dictionary.ContainsKey(3))
        {
            Console.WriteLine("Key 3 exists in the dictionary.");
        }
    }
}

 

① 하나의 배열이 주어지고, 해당 배열의 각 데이터는 숫자다.
배열 절반의 길이보다 큰 갯수만큼 특정 숫자가 들어가 있으면 해당 숫자,그렇지 않으면 -1을 리턴하는 함수를 만들어라. 

예: [2,7,7,7,1,7,2], 리턴값 : 7

using System;
using System.Collections.Generic;

class Program
{
    static int FindMajorityElement(int[] arr)
    {
        // 숫자와 빈도수를 저장하는 Dictionary 생성
        Dictionary<int, int> counts = new Dictionary<int, int>();
        
        // 각 숫자의 빈도수를 계산
        foreach (int number in arr)
        {
            if (counts.ContainsKey(number))
            {
                counts[number]++;
            }
            else
            {
                counts[number] = 1;
            }
        }
        
        // 배열의 길이의 절반을 계산
        int halfLength = arr.Length / 2;
        
        // 빈도수가 배열 길이의 절반보다 큰 숫자를 찾음
        foreach (var entry in counts)
        {
            if (entry.Value > halfLength)
            {
                return entry.Key;
            }
        }
        
        // 해당하는 숫자가 없는 경우 -1을 리턴
        return -1;
    }
    
    static void Main()
    {
        // 예제
        int[] arr = {2, 7, 7, 7, 1, 7, 2};
        int result = FindMajorityElement(arr);
        
        Console.WriteLine("리턴값: " + result); // 리턴값: 7
    }
}

 

 

② 최빈값 찾기: 주어진 배열에서 가장 많이 등장하는 숫자를 찾아서 반환하라. 만약 두 개 이상의 숫자가 같은 횟수로 등장한다면, 그 중 아무 숫자나 반환하라.

using System;
using System.Collections.Generic;

class Program
{
    static int FindMode(int[] arr)
    {
        if (arr == null || arr.Length == 0)
        {
            throw new ArgumentException("Input array should not be null or empty");
        }

        Dictionary<int, int> counts = new Dictionary<int, int>();

        // 배열의 모든 요소에 대해 반복하여 빈도수를 계산한다.
        foreach (int number in arr)
        {
            if (counts.ContainsKey(number))
            {
                counts[number]++;
            }
            else
            {
                counts[number] = 1;
            }
        }

        int maxCount = 0; // 최대 빈도수를 저장할 변수.
        int mode = 0; // 최빈값을 저장할 변수.

        // Dictionary의 모든 항목에 대해 반복한다.
        foreach (var entry in counts)
        {
            if (entry.Value > maxCount) // 현재 항목의 빈도수가 최대 빈도수보다 크면
            {
                maxCount = entry.Value; // 최대 빈도수를 현재 항목의 빈도수로 갱신한다.
                mode = entry.Key; // 최빈값을 현재 숫자로 설정한다.
            }
        }

        return mode; // 최빈값을 반환한다.
    }

    static void Main()
    {
        int[] arr = { 4, 1, 2, 2, 3, 3, 4 };

        try
        {
            int mode = FindMode(arr); // FindMode 메서드를 호출하여 최빈값을 얻는다.
            Console.WriteLine("Mode: " + mode); // 최빈값을 콘솔에 출력한다.
        }
        catch (ArgumentException e) // ArgumentException이 발생하면
        {
            Console.WriteLine(e.Message); // 예외 메시지를 콘솔에 출력한다.
        }
    }
}

 

 

 

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

namespace MYTCP
{

    //라이브러리 형태로 작성될 크래스.
    //TCP클라이언트로 접속할 수 있는 클래스를 작성.
    public class Client
    {
        //이벤트
        //연결이되면 연결을 알려주는 이벤트
        public delegate void ConnectedHandler(Socket s);
        public delegate void DisConnectedHandler(string disconnectMsg);
        public delegate void ReceivedHandler(byte[] bytes);
        public event ConnectedHandler OnConnected;
        public event DisConnectedHandler OnDisConnected;
        public event ReceivedHandler OnReceived;

        private Socket socket = null;
        private string ip = "";
        private int port = 43210;
        private Thread thread = null;
        private bool stop = false; //클라이언트가 연결이 종료됬는지.연결이 되있는 상태인지..

        public Client(string ipAddress,int port)
        {
            this.ip = ipAddress;
            this.port = port;
            //ip주소와,port를 할당.
            //127.120.120.255
            //데이터 중복없이 신뢰성있는 양방향 연결 기반의 바이트스트림
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        //클라이언트 연결을 시작
        public void Start()
        {
            try
            {
                //IPEndPoint클래스란? 끝점,즉 연결될 곳의 IP주소와 Port를 가지고있는 클래스
                //IP주소->어떠한 컴퓨터(서버)로 접속을 요청할 것인지..
                //PORT ->서버의 어떠한 프로그램에 접속을 요청할 것인지.,
                IPEndPoint remoteEp = new IPEndPoint(IPAddress.Parse(ip), port);
                socket.Connect(remoteEp);//서버에게 연결을 요청하게됨..->Connect 동기적으로 요청.->서버 한대만을,서버프로그램이라면 BeginConnect함수를 사용.
                                         //실패하면
                if (OnConnected != null)
                {
                    //연결이되면 이벤트를 통해서 사용하는 곳에 알려준다. 
                    OnConnected(socket);
                }

                //서버와 송수신 즉 데이터를 주고받아야됨.
                thread = new Thread(ReceiveMassage);
                thread.IsBackground = true;//프로그램이 종료되면
                thread.Start();
            }
            catch (Exception)
            {
                //서버와 연결실패시
                throw;
            }


        }

        //데이터수신 함수
        private void ReceiveMassage()
        {
            //클라이언트 연결이 끊긴게아니라면 계속해서 반복해서 수신하도록..
            while(!stop)
            {
                try
                {
                    //네트워크에서 바이트형태로 데이터를 주고
                    byte[] buffer = new byte[1024];//1024바이트

                    int bytesRead = socket.Receive(buffer,0,buffer.Length,SocketFlags.None);//데이터 수신.

                    if (bytesRead<=0)
                    {
                        throw new SocketException();
                    }

                    //buffer 실질적으로 데이터가 쓰인것을
                    Array.Resize(ref buffer, bytesRead);

                    if(OnReceived!=null)
                    {
                        OnReceived(buffer);
                    }

                }
                catch
                {
                    if(OnDisConnected!=null)
                    {
                        OnDisConnected("Disconnected");
                    }

                    //소켓연결 끊는작업
                    if (socket!=null)
                    {
                        socket.Close();
                    }

                    break;
                }
            }

            //클라이언트 연결이 끊겼거나 또는 예외가 발생했을 때.
            this.Stop();
        }

        public void SendMessage(string message)
        {
            int bytesSend = socket.Send(Encoding.UTF8.GetBytes(message));//UTF8->Bytes
        }

        //직접적으로 로그아웃또는 
        public void Stop()
        {
            stop = true;
            if(socket!=null)
            {
                socket.Close();
                socket.Dispose();
            }
            socket = null;

            //수신하는 쓰레드도 정지
            if(thread!=null)
            {
                thread.Abort();
            }
            thread = null;
        }
    }
    
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MYTCP
{
    public partial class Form1 : Form
    {
        //클라이언트가 주기적으로 메시지를 보내기위해서
        //Timer
        //소스코드는 앞서저기가 
        bool timerStop = false;
        System.Timers.Timer timer = null;
        public delegate void AsncMethodCaller(Client cnt);
        //
        DateTime receivedSuccessTime = DateTime.Now.AddMilliseconds(10 * 1000);//현재시간+10초


        public Form1()
        {
            InitializeComponent();
        }

        Client client = null;

        private void button1_Click(object sender, EventArgs e)
        {
            Start();
        }

        private void Start()
        {
            if(client == null)
            {
                receivedSuccessTime = DateTime.Now.AddMilliseconds(10 * 1000);
                client = new Client(textBox1.Text, Int32.Parse(textBox2.Text));
                client.OnConnected += TcpConneted;
                client.OnDisConnected += TcpDisConnected;
                client.OnReceived += TcpReceived;
                client.Start();//서버와의 연결이 이뤄지게된다.

                //클라이언트가 주기적으로 메시지를 보내기위해서
                timerStop = false;
                timer = new System.Timers.Timer();
                timer.Interval = 10 * 1000;//테스트
                timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elasped);
                timer.AutoReset = true;
                timer.Start();
            }
        }

        
        //주기적으로 요청
        private void timer_Elasped(object sender,System.Timers.ElapsedEventArgs e)
        {
            if(!timerStop)
            {
                //시간비교차를 이용해서 연결이 송수신을 통해서 정상적인..
                bool validConnection = ValidConnection(receivedSuccessTime, 15);
                if (validConnection&& client!=null)
                {
                    //주기적으로 메시지를 보내고
                    //비동기적으로
                    AsncMethodCaller asncMethodCaller = new AsncMethodCaller(DoPolling);
                    asncMethodCaller.BeginInvoke(client, null, null);
                    //보낸게 일정시간내에 도착을 하면... 연결이 정상이라고 판단을 할수있다.
                }
                else
                {
                    //연결이 비정상적이면
                    Stop();

                    //재연결을 하겠다?
                    Start();
                }

            }
        }

        //메시지를 실질적으로 보내게될 함수,
        private void DoPolling(Client cnt)
        {
            if(client!=null)
            {
                client.SendMessage("polling123");
            }
        }

        //15초내로 
        private bool ValidConnection(DateTime dt,int limitTime)
        {
            //마지막으로 수신한 시간과 현재시간차를 계산.
            int diffSeconds = Convert.ToInt32(DateTime.Now.Subtract(dt).TotalSeconds);//총시간중에 초단위로 시간차를
            if(diffSeconds> limitTime)
            {
                return false;
            }
            else
            {
                //폴링이라던지 기타데이터를 수신하게되면
                return true;
            }
        }

        private void TcpConneted(Socket s)
        {
            this.BeginInvoke(new Action(() =>
            {
                richTextBox1.Text += "[Local]서버접속 성공.\r\n";
            }));
        }

        private void TcpDisConnected(string disconnectMsg)
        {
            this.BeginInvoke(new Action(() =>
            {
                richTextBox1.Text += "[Local]서버접속 해제됨.\r\n";
            }));

            this.TimerStop();
            client = null;
        }

        private void TcpReceived(byte[] bytes)
        {
            receivedSuccessTime = DateTime.Now;//데이터를 수신한 시각
            this.BeginInvoke(new Action(() =>
            {
                richTextBox1.Text += "[Local]수신메시지-" + Encoding.UTF8.GetString(bytes) + ".\r\n";
            }));
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Stop();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Stop();
        }

        private void Stop()
        {
            this.TimerStop();
            if (client != null)
            {
                client.Stop();
            }
            client = null;
        }

        private void button3_Click(object sender, EventArgs e)
        {
            if(client!=null)
            {
                client.SendMessage(textBox3.Text);//클라이언트에서 서버로 메시지전송.
            }
        }

        //대문자소문자 섞어서 쓰는데.. 통일
        private void TimerStop()
        {
            timerStop = true;
            if(timer!=null)
            {
                timer.Elapsed-= new System.Timers.ElapsedEventHandler(timer_Elasped);
                timer.Stop();
                timer.Dispose();
                timer = null;
            }
        }
    }
}

 

 

Source : C#.Net 0.5년차~3년차(파트2)

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-2&unitId=77904&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

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

데이터 바인딩  (0) 2023.06.28
dictionary 설명과 dictionary을 활용한 2문제  (0) 2023.06.21
전기제어 PLC(Programmable Logic Controller)  (0) 2023.06.19
C# 파일과 디렉터리  (0) 2023.06.19
dynamic  (0) 2023.06.18

+ Recent posts