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 확장을 통해 작성 해 보았다.

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

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