2019/07/26

C# 문자열 포함 여부 확인하기.


ToUpper() 를 사용하면 불필요한 문자열을 생성하므로 좋은 방법은 아니다.

string text = "This is an apple.";
string apple = "Apple.";

bool contains = text.ToUpper().Contains(apple.ToUpper());

IndexOf(string, StringComparison) 사용을 추천 한다.

bool contains = text.IndexOf(apple, StringComparison.OrdinalIgnoreCase) >= 0;

확장 함수를 사용하면 더 좋을 것 이다.

public static class StringExtensions
{
    public static bool Contains(this string thisString, string value, StringComparison stringComparison)
    {
        return thisString.IndexOf(value, stringComparison) >= 0;
    }
}

bool contains = text.Contains(apple, StringComparison.OrdinalIgnoreCase);

2019/01/06

interface 확장


확장 메서드는 static 메서드를 instance 메서드처럼 호출 할 수 있도록 해준다.

public static class ExtensionMethodSample
{
    public static double Round(this double value)
    {
        return Math.Round(value);
    }
}

//test
double value = 1.7;
Console.WriteLine(value.Round());
//static 메시드와 같은 식으로도 호출 가능하다.
Console.WriteLine(ExtensionMethodSample.Round(value));

interface 를 확장하면 확장된 interface를 구현하는 클래스들은 부모 클래스의 정의된 메서드처럼 사용가능하다.

인터페이스 확장:
public interface IName
{
    string FirstName { get; }
    string LastName { get; }
}

public static class NameExtensions
{
    public static string FullName(this IName name)
    {
        return $"{name.FirstName} {name.LastName}";
    }
}

확장된 인터페이스 사용:
public class Member : IName
{
    public string FirstName { get; set; }

    public string LastName { get; set; }
}

public class Worker : IName
{
    public string FirstName { get; set; }

    public string LastName { get; set; }
}

결과 테스트:
//test
Member member = new Member
{
    FirstName = "Member",
    LastName = "Lee"
};

Console.WriteLine(member.FullName());

Worker worker = new Worker
{
    FirstName = "Worker",
    LastName = "Lee"
};

Console.WriteLine(worker.FullName());

실제 Member 와 Worker 에는 FullName() 메서드는 없다.

interface 확장으로 class 수정 없이 기능을 확장 할 수 있다.

2019/01/02

C# 문제, 20190102

#1
아래 클래스는 Tree Item 을 표현하였다.

public class TreeItem
{
    public TreeItem Before { get; private set; }

    private readonly List<TreeItem> nexts = new List<TreeItem>();
    public IEnumerable<TreeItem> Nexts => this.nexts;

    public string Name { get; }

    public TreeItem(string name)
    {
        this.Name = name;
    }

    public void AddNext(TreeItem item)
    {
        this.nexts.Add(item);
        item.Before = this;
    }

    public IEnumerable<TreeItem> BeforeStream()
    {
        //
    }

    public IEnumerable<TreeItem> NextStream()
    {
        //
    }
}

Before는 부모 노드 이며 Nexts 자식 노드들이다.

메서드 BeforeStream() 은 모든 부모들을 검색한다.
메서드 NextStream() 은 모든 자식들을 검색한다.

이 두 메서드를 지연 반환(yield return)을 이용하여 작성 하고 아래와 같은 결과를 출력한다.

public static void Test()
{
    TreeItem root = new TreeItem("root");

    TreeItem level1 = new TreeItem("level1");
    TreeItem level2 = new TreeItem("level2");

    TreeItem level1_1 = new TreeItem("level1_1");
    TreeItem level1_2 = new TreeItem("level1_2");

    TreeItem level2_1 = new TreeItem("level2_1");
    TreeItem level2_2 = new TreeItem("level2_2");
    TreeItem level2_3 = new TreeItem("level2_3");

    root.AddNext(level1);
    root.AddNext(level2);

    level1.AddNext(level1_1);
    level1.AddNext(level1_2);

    level2.AddNext(level2_1);
    level2.AddNext(level2_2);
    level2.AddNext(level2_3);

    Console.WriteLine("root nexts:");
    foreach (var item in root.NextStream())
    {
        Console.WriteLine(item.Name);
    }

    Console.WriteLine();
    Console.WriteLine("level1_2 befores:");
    foreach (var item in level1_2.BeforeStream())
    {
        Console.WriteLine(item.Name);
    }
            
    var found = root.NextStream().First(i => i.Name == "level1_1");

    Console.WriteLine("");
    Console.WriteLine("found:");
    Console.WriteLine(found.Name);
}

결과:
root nexts:
level1
level1_1
level1_2
level2
level2_1
level2_2
level2_3

level1_2 befores:
level1
root

found:
level1_1

#2
작성한 두 메서드(BeforeStream, NextStream)를 다른 곳 에서도 사용 할 수 있도록 하자.

아래와 같이 조직도를 표현하기 위한 Group(조직) 이라는 Entity 가 있다.
Group은 GroupBase라는 클래스를 상속 받고 있으므로 해당기능은 인터페이스로 정의하고 확장메서드를 이용하여 작성한다.  작성한 인터페이스를 Group에 적용 시킨다. (결과는 #1과 동일)

public class GroupBase
{ }

public class Group : GroupBase 
{
    public Group Before { get; private set; }

    private readonly List<Group> nexts = new List<Group>();
    public IEnumerable<Group> Nexts => this.nexts;

    public string Name { get; }

    public Group(string name)
    {
        this.Name = name;
    }

    public void AddNext(Group group)
    {
        this.nexts.Add(group);
        group.Before = this;
    }
}

/*답*/

ITreeSearch<T> 를 작성하고 이를 확장하여 BeforeStream, NextStream 을 작성하였다.

Group 에 Tree Search 기능 추가.
public class Group : GroupBase, ITreeSearch<Group>


수정 할 수 없는 다른 에셈블리에 아래와 같은 클래스가 있다면
public class ClassInOtherAssembly
{
    public ClassInOtherAssembly Parent { get; }

    public IEnumerable<ClassInOtherAssembly> Children  { get; }
}


아래와 같이 기능을 추가 할 수 있다.
public class ClassInOtherAssemblyExt : ClassInOtherAssembly, ITreeSearch<ClassInOtherAssembly>
{
    public ClassInOtherAssembly Before => this.Parent;

    public IEnumerable<ClassInOtherAssembly> Nexts => this.Children;
}


다음은 작성한 ITreeSearch<T>와 TreeSearchExtensions 이다.
public interface ITreeSearch<T>
{
    T Before { get; }
    IEnumerable<T> Nexts { get; }
}

public static class TreeSearchExtensions
{
    public static IEnumerable<T> BeforeStream<T>(this ITreeSearch<T> treeSearch) where T : ITreeSearch<T>
    {
        var current = treeSearch.Before;

        while (current != null)
        {
            yield return current;

            current = current.Before;
        }
    }

    public static IEnumerable<T> NextStream<T>(this ITreeSearch<T> treeSearch) where T : ITreeSearch<T>
    {
        foreach (var next in treeSearch.Nexts)
        {
            yield return next;

            foreach (var item in NextStream(next))
            {
                yield return item;
            }
        }
    }
}

제약 조건으로 형식 T 는 ITreeSearch<T> 임을 정의 하여 검색된 Before 와 Next 가 ITreeSearch<T> 임을 나타 내었다.

재사용 가능한 메서드를 Interface 확장을 통해 작성 해 보았다.

2018/06/20

How to initialize all the settings in 'Visual Studio'. - Solve the problem that debugging does not work.


Run: Start->Visual Studio->Developer Command Prompt.

Inputs the initialization command:
devenv /resetuserdata

In this way, I solved a problem 'Debugging not working'.

2018/06/19

Extending the Shown Event in WPF


Using the Shown Event.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var eventExtend = this.EventExtend();
        eventExtend.AddShownEventHandler(this.MainWindow_Shown);
    }

    public void MainWindow_Shown(object sender, EventArgs e)
    {
            
    }
}

Fire the Shown Event using the LayoutUpdated Event.


public class FrameworkElementEventExtend
{
    public event EventHandler Shown;

    private bool initialized;
    private readonly FrameworkElement frameworkElement;

    public FrameworkElementEventExtend(FrameworkElement frameworkElement)
    {
        this.frameworkElement = frameworkElement;

        this.frameworkElement.LayoutUpdated += FrameworkElement_LayoutUpdated;
    }

    private void FrameworkElement_LayoutUpdated(object sender, EventArgs e)
    {
        if (!this.initialized && (this.frameworkElement.ActualHeight > 0 || this.frameworkElement.ActualWidth > 0))
        {
            this.Shown?.Invoke(this, EventArgs.Empty);

            this.initialized = true;

            this.frameworkElement.LayoutUpdated -= FrameworkElement_LayoutUpdated;
        }
    }
}

public static class FrameworkElementEventExtendHelper
{
    public static FrameworkElementEventExtend EventExtend(this FrameworkElement frameworkElement)
    {
        FrameworkElementEventExtend eventExtend = new FrameworkElementEventExtend(frameworkElement);
        return eventExtend;
    }

    public static void AddShownEventHandler(this FrameworkElementEventExtend eventExtend, EventHandler eventHandler)
    {
        eventExtend.Shown += eventHandler;
    }
}

2018/04/19

C# IDisposable구현의 기본 패턴

안전하고 일관성있는 관리를 위한 기본 삭제 패턴이다.

관리되지 않는 리소스를 포함하는 형식은 IDisposable을 구현한다.

해당 형식을 상속받는 형식에서의 리소스 관리도 고려 해야 한다.
이를 위하여 가상(virtual)함수를 사용한다.

해당 인스턴스 사용 후에는 Dispose 함수를 호출하여 리소스 삭제를 가능하게 한다.

Dispose 함수 호출의 누락을 대비하여 소멸자를 구현한다.

Dispose 함수 호출로 리소스를 삭제한다면 소멸자 호출이 필요 없으므로 이를 GC에 알린다. 소멸자 호출을 필요로 하는 인스턴스는 GC에의한 수집이 늦어 지기 때문이다.

기본 IDisposable 구현
public class MyUnmanagedClass : IDisposable
{
    IntPtr unmanagedResource;
    bool disposed = false;

    public MyUnmanagedClass()
    {
        //비관리 리소스 할당.
        //unmanagedResource = ...
    }

    public void Dispose()
    {
        this.Dispose(true);
        //소멸자를 호출 하지 않도록 설정.
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                // 관리 리소스 해지.
            }

            // 비관리 리소스 해지.
            unmanagedResource = IntPtr.Zero;

            this.disposed = true;                
        }
    }

    ~MyUnmanagedClass()
    {
        this.Dispose(false);
    }
}

하위 형식에서의 리소스 관리
public class MyUnmanagedClass2 : MyUnmanagedClass
{
    bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                // 관리 리소스 해지.
            }

            // 비관리 리소스 해지.                

            this.disposed = true;
        }

        base.Dispose(disposing);
    }
}

사용예
public static class TestClass
{
    public static void Test()
    {
        var myUnmanagedClass = new MyUnmanagedClass();
        myUnmanagedClass.Dispose();

        using (var myUnmanagedClass2 = new MyUnmanagedClass2())
        { }
    }
}

2018/03/20

WinForm Title Bar 없이 폼이동

MouseDown, MouseMove, MouseUp 이벤트를 이용하여 폼이동을 처리 한다.

MouseDown 시점에 Control.MousePosition 값과 MouseMove 시점 Control.MousePosition 값의 차이 만큼 폼을 이동 한다..

샘플은 폼 또는 Label 을 마우스로 이동하여 폼을 이동 한다.

Sample Form

기능 구현.

public static class FormHelper
{
    public static void SetMovingForm(this Form form, Control[] triggerControls, Cursor movingCursorOrNull = null)
    {
        bool mouseDown = false;
        Point lastMousePoint = new Point();
        Cursor movingCursor;

        if (movingCursorOrNull == null)
            movingCursor = Cursors.NoMove2D;
        else
            movingCursor = movingCursorOrNull;

        Dictionary<Control, Cursor> oldCursors = new Dictionary<Control, Cursor>();

        foreach (var item in triggerControls)
        {
            Control con = item;

            con.MouseDown += (s, e) =>
            {
                var thisControl = s as Control;

                mouseDown = true;

                if (!oldCursors.ContainsKey(item))
                    oldCursors.Add(item, thisControl.Cursor);
                else
                    oldCursors[item] = thisControl.Cursor;

                thisControl.Cursor = movingCursor;

                lastMousePoint = Control.MousePosition;
            };                

            con.MouseMove += (s, e) =>
            {
                if (!mouseDown)
                    return;

                Point currentPoint = Control.MousePosition;

                int x = currentPoint.X - lastMousePoint.X;
                int y = currentPoint.Y - lastMousePoint.Y;

                form.Location = new Point(form.Location.X + x, form.Location.Y + y);

                lastMousePoint = currentPoint;
            };

            con.MouseUp += (s, e) =>
            {
                var thisControl = s as Control;

                mouseDown = false;
                thisControl.Cursor = oldCursors[thisControl];
            };
        }
    }
}

사용예.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.FormBorderStyle = FormBorderStyle.None;

        this.SetMovingForm(new Control[] { this, this.titleLabel });
    }
}

C# 문자열 포함 여부 확인하기.

ToUpper() 를 사용하면 불필요한 문자열을 생성하므로 좋은 방법은 아니다. string text = "This is an apple." ; string apple = "Apple." ; bool ...