반응형
Console.WriteLine(default(int));  // output: 0
Console.WriteLine(default(object) is null);  // output: True

void DisplayDefaultOf<T>()
{
    var val = default(T);
    Console.WriteLine($"Default value of {typeof(T)} is {(val == null ? "null" : val.ToString())}.");
}

DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
// Default value of System.Nullable`1[System.Int32] is null.
// Default value of System.Numerics.Complex is (0, 0).
// Default value of System.Collections.Generic.List`1[System.Int32] is null.

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/default

반응형

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

linq (3)  (0) 2023.04.10
linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
반응형

 

 

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

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //join
            {

                List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            //player와 levels를 조인하는데
                    on p.Level equals l     //레벨 i 와 player.level 이 같은것만 조인한다
                    select p;

                foreach(var p in playerLevels)
                {
                    Console.WriteLine(p.Level);
                }
                int test = 0;
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

player와 levels를 조인하는데 레벨 i 와 player.level 이 같은것만 조인하게 한다

 

                 List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            
                    on p.Level equals l    

                    select p;

 

 

 

 

 

표준 Linq 방식

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }


            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);


                int iii = 0;
            }

두개의 결과가 같은 것을 볼 수 있다

 

            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);

 

결과 : 

세부적인 기능을 사용하고 싶다면 함수형 linq 를 사용하면 된다

 

반응형

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

default 연산자  (0) 2023.04.25
linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
반응형
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }
            


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

palyer 에 

 public List<int> Items { get; set; } = new List<int>();

를 추가 한다음

 

from from 구문으로 item 의 모든 목록을 출력하는 구문이다

 

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

 

 

 

 

Group 처리

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

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
                int jj = 0;
            }


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

코드 중

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
               
            }

이 부분이 플레이어들을 같은 level 로 그룹화 한다음 각 그룹끼리는 key 값에 의해 정렬 되도록 처리한 것이다

into 는 그룹화 한것을 g 라는 집합으로 넣겠다는 것

 

결과 : 

 

반응형

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

default 연산자  (0) 2023.04.25
linq (3)  (0) 2023.04.10
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
반응형
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                _players.Add(player);
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

 

linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
from 은 foreach 로 생각해도 괜찮다
실행 순서는 from where orderby select 순으로실행된다 생각하면 된다

=> 이것이 원래 db 의 쿼리 구문을 이해하는 순서인데 c# 의 linq 에서는 select를 아예 밑으로 내려놨음


            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

 

 

 

유니티에선 linq 가 ios 버전에서 문제가 발생 할 수 있어서(불안전해서) 안쓰는게 좋다

(현재 버전 확인 필요)

 

서버에선 linq 활용을 하기에 DB 와의 명령어등의 호환성 좋아서 사용하는것은 괜찮다

 

 

db 관련 참고글 https://3dmpengines.tistory.com/category/%EC%84%9C%EB%B2%84%28Server%29/DB

 

'서버(Server)/DB' 카테고리의 글 목록

[3D] [M]athematic [P]hysics Engines

3dmpengines.tistory.com

 

반응형

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

linq (3)  (0) 2023.04.10
linq (2)  (0) 2023.04.09
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
반응형

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        //async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

        //task 는 일감을 말함
        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

 

Task 를 생성하고 delay 에 시간을 넣으면 생성과 동시에 시간이 흘러간다는 것을 알 수 있다

 

start test 문자가 뜨고 시간 지연 없이 바로 while start 가 호출되는데

        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            //처리 지점
              
            t.Wait();
            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }

wait 이 들어가면 start test 가 찍힌 후 3초 이후에 while start 가 호출 되는것을 알 수 있다

 

그래서 //처리 지점    에서 task 가 완료 되는 동안 다른 동작을 할 수 있게 된다

 

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);

            await t;
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

이 코드는 async/await 인데 결과는 다음과 같다

async 함수가 호출되고 task 가 완료 될때까지 await 으로 기다려지긴 하지만 제어권이 main 으로 넘어가서 처리할 것을 먼저 처리 할수 있는 형태가 된다 typescript 에 이와 유사한 문법들이 있다

 

또는 다음 처럼 간략한 버전으로 사용 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

 

main 에서 testAsync 함수가 완료 될때까지 대기 시킬 수도 있다 다음 처럼 대기 할수 있고

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            await testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

testAsync 함수가 모두 완료 될때까지 awiat testAsync(); 구문에서 대기하다가 완료 된 이후 main 의 나머지 구문을 이어 나간다

결과는 다음과 같다

 

 

값을 리턴 받고 싶다면 Task<T> 를 리턴하면 된다

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await testAsync();

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

다음처럼 main 에서 다른일을 하기위해 task 를 별도의 변수로 두어 위 async 함수를 분리하여 동시에 처리할 일을 처리 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            Task<int> t = testAsync();
            Console.WriteLine("다른 일");
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await t;

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

예시 다음 처럼 조식을 만들때

다음 처럼 일반적인 방식인 순차적으로 생각 할 수도 있지만

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

 

async 함수를 적절히 await 하여 처리 하여 대기 시간을 줄일 수 있다

그림은 위에서 아래로 내려가는 방향의 시간의 흐름이고 옆은 비동기로 실행 되는 개수라 보면 된다

 

 

 

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

 

 

최종 방식은 List 로 Task 를 담아 대기 하는 방식으로 최종 15 분 안에 끝나는 예시이다

 

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

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);

            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                await finishedTask;
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

ref : https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/

 

반응형

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

linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
반응형
  • 배열(예를들면 네트워크에서 받은 데이터를 저장한 버퍼 byte[] buffer)의 특정 위치에서 특정 크기만큼 참고하고 싶을 때 보통 새로 배열을 만든 후 복사해야 원하는 데이터만을 참조할 수 있다.
  • 그러나 ArraySegment를 사용하면 새로 배열을 만들지 않으면서 버퍼의 특정 데이터를 참조할 수 있다.
  • ArraySegment를 사용하여 1차원 배열의 랩퍼로 배열 내의 요소를 범위(시작 위치와 길이)를 지정하여 구별한다.
int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 배열을 나눈다. ary1의 범위만큼의 참조를 만든다.
ArraySegment<int> arySeg = new ArraySegment<int>(ary1, 2, 5);

// 나누어진 범위의 요소를 출력한다.
for (int i = arySeg.Offset; i < arySeg.Offset + arySeg.Count; i++)
{
	Console.WriteLine("{0}:{1}", i, arySeg.Array[i]);
}

아래와 같이 표시된다

2:2
3:3
4:4
5:5
6:6

 

ref : https://jacking75.github.io/csharp_ArraySegment/

반응형

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

Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
C# 에서 Dispose  (0) 2022.12.04
반응형

멀티 스레드 환경에서 각 스레드별로 하나의 공통 데이터에 접근하여 처리 될때 스레드 마다 원자성을 보장하면서
데이터를 다뤄야 하는 상황이 발생할 수 있습니다.

이때 제공 되는것이 Thread Local Storage(TLS) 변수라는 것이 있습니다.

쉽게 말해 tls변수는 스레드별 고유한 데이터를 저장(?)할 수 있는 공간이라고 이해 하면 됩니다. 또한 이 tls변수는(이하 거론되는 ThreadLocal<T> AsyncLocal<T> 동일) 자체적으로 데이터 원자성이 보장되기에 lock없이 접근하여 사용이 가능합니다.

닷넷 C#에서는 이것을 System.ThreadStaticAttribute 어트리뷰트 클래스로 제공 하고 있으며
System.Threading.ThreadLocal<T> 그리고 System.Threading.AsyncLocal<T> 클래스로 제공하고 있고, C++에서는 __declspec(thread)로 사용 가능합니다.

System.Threading.ThreadLocal<T> 같은 경우 System.ThreadStaticAttribute 보다 변수 초기 방법을 제공하고 있어 [ThreadStatic] 어트리뷰트 보다 좀 더 최신 기능을 제공합니다.

이번 포스트는 System.Threading.ThreadLocal<T>  System.Threading.AsyncLocal<T> 가 서로 어떤 부분에 차이점이 있는지에 대한 내용 입니다.

자 그럼 어떻게 다른지 살펴보겠습니다.

ThreadLocal<T> AsyncLocal<T> 차이점

위 tls역할을 하는 두 클래스의 큰 차이점은 스레드풀을 사용하는 스레드 환경에서 차이점이 있습니다.

우선 System.Threading.ThreadLocal<T> 은 해당 스레드가 스레드풀에 반환되어도 해당 데이터는 항상 유지 되어 집니다.

private static ThreadLocal<int> _tl = new ThreadLocal<int>();

private static async Task WorkAsync(int i)
{
  if(_tl.Value == 0)
  {
    _tl.Value = i;
    Console.WriteLine($"[새로운 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
  }
  else
  {
    Console.WriteLine($"[기존 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
  }
  Console.WriteLine("-----------------------------------------");
}

위 처럼 System.Threading.ThreadLocal<T> 을 사용하는 메서드를 스레드로 호출해 봅니다. 그리고 차이점을 확실히 보기 위해 스레드풀의 스레드 개수 제한을 4개로 설정해 보았습니다.

// ThreadPool의 개수를 4개로 임의 제한
ThreadPool.SetMinThreads(4, 4);
ThreadPool.SetMaxThreads(4, 4);

for (int i = 1; i < 11; i++)
{
  await Task.Run(() => WorkAsync(i));
}

스레드를 사용하여 WorkAsync() 메서드를 10번 호출하였고 파라메터로 tls값을 동시에 넘겨 주었습니다.

결과는 어떻게 나올까요?

위 처럼 처음 스레드가 사용되어서 tls값이 설정 되고 해당 스레드가 반환된 이후 다시 같은 스레드가 사용 되었을때
tls값이 처음 설정 된 값으로 유지 되고 있는 걸 확인 할 수 있습니다.

반면,

AsyncLocal<T> 는 어떻게 처리 되는지 위 코드에서 System.Threading.ThreadLocal<T> 부분만 바꿔서 실행 시켜 보겠습니다.

결과를 보니 위 결과와는 다르게 사용된 스레드가 스레드풀에 반환될때는 값이 초기화가 되는 걸 볼 수 있습니다.

 

ref :https://blog.arong.info/c%23/2022/01/14/C-ThreadLocal-T-%EC%99%80-AsyncLocal-T-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90.html

반응형

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

async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
Common Memory Leaks In C#  (0) 2022.12.04
C# 에서 Dispose  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27
반응형

Introduction

We all know that the .NET framework is an object-oriented, memory-managed, and type-safe framework. C# is the main development language in the .Net framework that has the same qualities. When we talk about memory management, we offer to refer to the garbage collector (GC) which will reclaim any unused objects and hence release them from memory. This potentially means we do not need to worry about memory leaks. However, there might be situations where we might create memory leaks which would be difficult for the GC to collect or run out of memory. Let us look at some of these situations and how to avoid them. So, let us begin.

Memory Leaks in C#
 

Implementing the IDisposable pattern

Always remember to implement the Dispose method on a class that implements the IDisposable interface. Otherwise, a memory leak can be caused. The best way to do this is to use the “using” statement which will call the disposal method for every condition. If you cannot use the “using” statement, remember to do this manually and also suppress the Finalize method as this would not be required.

Very Long Running Threads

If you have implemented a very long-running or infinite running thread that is not doing anything and it holds on to objects, you can cause a memory leak as these objects will never be collected. The fix for this is to be very careful with long-running threads and not hold on to objects not needed.

Over Caching

It has been seen that in some applications there is too much caching. This might help to speed up your application in the beginning but if you continue to do this you will eventually run out of memory. The best solution is to only cache objects very frequently used and not add unnecessary objects to the cache. Also, keeping the size of the cache objects to a reasonable one is important.

Using Static Objects

The use of static objects can also cause memory leaks. The reason for this is that static objects and everything they reference are not collected by the Garbage collector. Hence, use them to a bare minimum.

Using Unmanaged Objects

Whenever we are working with objects that are not collected by the garbage collector, we need to be extra careful to dispose of them after use. Otherwise, these will cause a memory leak as they are not cleaned up by the GC.

Summary

In this article, we looked at some potential situations that could lead to memory leaks or we can say inefficient use of the memory in our C# code, and how this can be avoided. There might be other similar situations as well. Hence, it is always a good idea to ensure memory is not being held for longer than required. Happy coding!

 

 

ref : https://www.c-sharpcorner.com/article/common-memory-leaks-in-c-sharp/

반응형

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

C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
C# 에서 Dispose  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27
SpinLock  (0) 2022.11.27
반응형

C#은 가비지 컬렉터(GC)가 메모리를 자동으로 관리한다. 필요 없는 클래스의 인스턴스를 메모리에서 바로 지우는 게 아니라, 조건이 될 때까지 기다렸다가 지우기 때문에 클래스를 지웠다고 해도 그게 실제로 바로 삭제되는 것은 아니다. 일반적인 메모리라면 GC에 맡겨도 상관이 없지만, 관리되지 않는(Unmanaged, Native) 리소스는 즉각 해제해야 하는 경우가 생기는데, 그럴 때 필요한 것이 Dispose이다.

 

그래서, C++의 경우 소멸자에 각종 변수의 메모리를 해제하는 것으로 간단하게 구현이 될 만한 내용이지만, C#에서는 바로 삭제가 필요한 리소스를 해제하기 위해서 Dispose 함수가 필요하다.

 

소멸자와 Dispose의 차이 : stackoverflow.com/questions/339063/what-is-the-difference-between-using-idisposable-vs-a-destructor-in-c?newreg=eff4d2b40e844d5581272cc65983c418

 

What is the difference between using IDisposable vs a destructor in C#?

When would I implement IDispose on a class as opposed to a destructor? I read this article, but I'm still missing the point. My assumption is that if I implement IDispose on an object, I can

stackoverflow.com

그런데, Dispose를 호출하는 걸 깜박하면 어떻게 될까?

그럴 경우에는 GC에서 해당 인스턴스를 제거할 때, (소멸자에서) Dispose를 호출해준다.

큰 문제가 없으면 그냥 냅 둬도 상관없다는 얘기인데, 다만 다음과 같은 부작용이 생길 수 있다고 한다.

 

1.GC가 해당 인스턴스를 언제 지울 지 알 수 없다.

 즉, Dispose가 언제 호출될지 알 수 없다. 애플리케이션이 종료될 때까지 호출이 안될 수도 있다. 중요한 리소스를 사용하거나, 메모리를 많이 먹는 클래스라면 위험할 수도 있겠다.

 

2.GC가 해당 인스터스를 지울 때 Dispose를 호출하게 되면 그 순간 부하가 걸린다.

 즉, 랜덤하게 랙을 유발할 수도 있다. 성능에 예민한 앱이라면 주의가 필요하다.

 

3.GC가 해당 인스턴스를 지울 때 Dispose에서 예외가 발생하면 어플 자체가 크래시 할 수도 있다.

 수동으로 Dispose를 할 때에는 try/catch로 보호할 수 있지만, 자동제거일 경우 예외를 잡을 수 없기 때문에 매우 위험할 수도 있다.

 

참고 링크 : stackoverflow.com/questions/32397248/what-happens-if-i-dont-call-dispose

 

 

매번 Dispose를 호출하기 귀찮을 때는 using 블록을 사용하면 된다. using을 사용하면 자동으로 Dispose를 해준다.

using과 try/catch를 같이 사용해야 할 때에는 using을 try 안쪽에 넣으면 된다.

 

참고 링크 : theeye.pe.kr/archives/2736

 

Unity C# – IDisposable 알아보기 | 아이군의 블로그

이번에는 IDisposable 인터페이스에 대해서 알아보겠습니다. C#은 가비지콜랙터(Garbage Collector)를 가지고 있습니다. 이 GC는 기본적으로 관리되는 모든 객체들의 참조 링크를 관리하며 더이상 참조되

theeye.pe.kr

 

Dispose 기능이 필요한 커스텀 클래스를 만들어야 할 때에는 IDisposal을 상속 받아서 만들면 된다.

 

참고 링크  : medium.com/dotnetdev/idisposable-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95-4fa0fcf0e67a

 

IDisposable 패턴의 올바른 구현 방법

.NET Framework에서 새로운 클래스를 만들 때 여러가지 메모리 관리 디자인 패턴과 기법을 적용할 수 있지만, 한시적으로 사용해야 할 필요가 있는 자원들을 묶어서 관리할 때에는 IDisposable 패턴을

medium.com

참고 링크 2 : codeless.tistory.com/entry/C-Disposable-%ED%8C%A8%ED%84%B4

 

C# Disposable 패턴

public class MyResourceHog : IDisposable { private bool alreadyDisposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool isDisposing)..

codeless.tistory.com

 

 

ref : https://chomdoo.tistory.com/15

반응형

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

ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27
SpinLock  (0) 2022.11.27
c# AutoResetEvent, ManualResetEvent (커널모드)  (0) 2022.11.27
반응형

 

 

ReaderWriterLock 은 EnterWriterLock 호출 되기 전까진 EnterReadLock과 ExitReadLock  구간을 lock 없이 읽을 수 있는데 EnterWriterLock  이 호출 되면 이때 lock 이 점유되어 놓아지기 전까지 EnterReadLock ~ ExitReadLock   구간을 다른 스레드가 접근하지 못하게 된다

 

아이템 업데이트를 서버에서 잠깐 처리해야 할때 흔히자읂은 업데이트에서 사용 될 수도 있다

 

 

 

반응형
반응형

SpinLock 사용 예시

 

static SpinLock _lock

 

Enter 에서 현재 lock 을 제대로 잡았는지에 대한 상태를 lockTaken 변수를 통해서 알 수 있게 된다

 

Enter(Boolean) 메서드 호출에서 예외가 발생하는 경우에도 안정적인 방식으로 잠금을 얻으며 잠금을 얻었는지 확인하기 위해 lockTaken을 안정적으로 검사할 수 있습니다.
Exit() 잠금을 해제합니다.
Exit(Boolean) 잠금을 해제합니다.

 

 

//      SpinLock.IsThreadOwnerTrackingEnabled
    static void SpinLockSample2()
    {
        // Instantiate a SpinLock
        SpinLock sl = new SpinLock();

        // These MRESs help to sequence the two jobs below
        ManualResetEventSlim mre1 = new ManualResetEventSlim(false);
        ManualResetEventSlim mre2 = new ManualResetEventSlim(false);
        bool lockTaken = false;

        Task taskA = Task.Factory.StartNew(() =>
        {
            try
            {
                sl.Enter(ref lockTaken);
                Console.WriteLine("Task A: entered SpinLock");
                mre1.Set(); // Signal Task B to commence with its logic

                // Wait for Task B to complete its logic
                // (Normally, you would not want to perform such a potentially
                // heavyweight operation while holding a SpinLock, but we do it
                // here to more effectively show off SpinLock properties in
                // taskB.)
                mre2.Wait();
            }
            finally
            {
                if (lockTaken) sl.Exit();
            }
        });

 

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.spinlock?view=net-7.0

반응형
반응형

lock 클래스는 임의로 만든 lock 클래스이고 실제 중요한것은 AutoResetEvent 이다

 

초기 AutoResetEvent 값은 true 였고, 초기 값을 false 로 설정 하는것도 가능하다

 

waitOne() 함수를 만나 상태가 false 가 되면서 락을 소유하게 된다, 그래서 다른 스레드는 더이상 접근하게 못하게 되다가

 

.Set() 을 하는 순간 AutoResetEvent  가 다시 true 상태가 됨으로 이후 부턴 다른 스레드도

waitOne 에서 락을 획득하여 들어올 수 있게 된다

 

 

AutoResetEvent 는 waitOne 에 진입함과 동시에 자신의 상태를 false 로 만든다는 특징이 있다

 

 

 

 

이렇게 코드를 돌릴때 한쪽에서만 공유변수 _num 을 증가 또는 감소 할 수 있게 된다

 

 

 

 

 

ManualResetEvent

이건 문을 자동으로 닫진 않는다 즉 waitOne 에서 lock 을 획득하면 자도으로 ManualResetEvent 상태를 false 로 만들지 않는다는 것이고 이것을 수기로 해줘야 한다

 

ManualResetEvent.Reset();  false 로 바꾸는 함수

 

 

 

이렇게 하면 문제가 발생하는데

reset 과 watiOne 사이에 여러 스레드가 동시에 들어올 수 있기 때문에 정확한 처리가 되지 않는다

 

하지만 의도가 여러 스레드의 입장이 동시에 가능하게 할 것이라면 이 처리는 맞게 된다

 

 

 

ex ManualResetEvet 를 댐 같은것이라 생각하면 예시가 될 수 있는데

 

사용자가 게임 진입 버튼을 누르기 전까지 ManualResetEvet  에 의해 모든 스레드가 대기 하고 있다가

 

버튼 을 누르면 인터넷으로부터 데이터를 받아오거나 로컬 파일을 동시에 읽는다던가 맵을 이동하는 등

여러 스레드들을 버튼 누른 시점 이후부터 동작하게 하기위한 스레드 대기의 용도로 사용 할 수 도 있다

 

 

spinlock 은 유저모드에서 실행되는거라 cpu 타임만 갉아 먹는데

AutoResetEvent, ManualResetEvent 은 커널모드에서 돌아가는 것이기 때문에 context switch 가 일어나게 되어 성능이 유저모드에 비해 떨어 질 수가 있다

 

 

 

 

 

 

커널모드 동기화 중에 다른 것이 Mutex 가 있다

위의  WiatOne(문을 잠금) 으로 lock 을 소유하고 ReleaseMutex(문을 다시 연다) 를 통해서 mutex 의 lock 을 놓아준다

 

 

 

mutex 와 autoResetEvent 의 차이는

 

Mutex 는 lock을  잡을떄 카운팅을 할 수 있고 mutex 는 스레드 id 또한 갖고 있어서 

문제가 있을때 유용한데 추가 비용이 좀 더 들어간다

 

 

 

 

반응형
반응형

스레드 컨텍스트 스위칭을 일으킬 수 있는 명령들

 

반응형

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

SpinLock  (0) 2022.11.27
c# AutoResetEvent, ManualResetEvent (커널모드)  (0) 2022.11.27
Interlocked.CompareExchange(ref _lockecd, desired, expected)  (0) 2022.11.27
Deadlock 현상  (0) 2022.11.27
lock 과 Monitor 임계영역  (0) 2022.11.26
반응형

 

int ddd = Interlocked.CompareExchange(ref _lockecd, desired, expected);

 

 

CompareExchange 이걸 풀어보면

 

if( _lockecd == expected )

{

   _lockecd  = desired;

  return expected;

}else{

 return _locked;

}

 

같으면 expected 를 리턴하고

다르면 _locked 를 리턴한다

 

그리고 같으면 _locked 가 desired 값으로 바뀐다

 

 

그래서 아래 코드는 값이 바뀔때까지 계속 실행된다

 

 

 

    // AddToTotal safely adds a value to the running total.
    public float AddToTotal(float addend)
    {
        float initialValue, computedValue;
        do
        {
            // Save the current running total in a local variable.
            initialValue = totalValue;

            // Add the new value to the running total.
            computedValue = initialValue + addend;

            // CompareExchange compares totalValue to initialValue. If
            // they are not equal, then another thread has updated the
            // running total since this loop started. CompareExchange
            // does not update totalValue. CompareExchange returns the
            // contents of totalValue, which do not equal initialValue,
            // so the loop executes again.
        }
        while (initialValue != Interlocked.CompareExchange(ref totalValue, 
            computedValue, initialValue));
        // If no other thread updated the running total, then 
        // totalValue and initialValue are equal when CompareExchange
        // compares them, and computedValue is stored in totalValue.
        // CompareExchange returns the value that was in totalValue
        // before the update, which is equal to initialValue, so the 
        // loop ends.

        // The function returns computedValue, not totalValue, because
        // totalValue could be changed by another thread between
        // the time the loop ends and the function returns.
        return computedValue;
    }

 

주석 설명들에 어떤것과 어떤것이 비교가 되는지 빠져 있어 헷갈린다

ms 홈페이지 설명글은 항상 느끼는 거지만 글자만 있지 내용을 파악하기엔 내용이 부실하게 빠져 있거나 부족하거나 이상한 부분들이 많다

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.interlocked.compareexchange?view=net-7.0

반응형
반응형

 

    class SessionManager
    {
        static object _lock_session = new object(); 
        public static void TestSession()
        {
            lock(_lock_session)
            {}
        }
        public static void Test()
        {
            lock (_lock_session)
            {
                UserManager.TestUser(); 
            }
        }
    }
    class UserManager
    {
        static object _lock_user = new object();
        public static void TestUser()
        {
            lock(_lock_user)
            {}
        }
        public static void Test()
        {
            lock(_lock_user)
            {
                SessionManager.TestSession(); 
            }
        }
    }

    class Program
    {
            

        static int number = 0;
        static object _obj = new object(); 

        static void Thread_1()
        {
            for(int i=0; i<100; i++)
            {
                SessionManager.Test(); 
                
            }
        }

        static void Thread_2()
        {
            for(int i=0; i<100; i++)
            {
                UserManager.Test(); 
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine("데드락에 걸리지 않았다!");
        }
    }

 

 

Thread_1 에서 SessionManger.Test를 호출할 때 _lock_session을 사용해서 Lock을 걸고

이때 동시에 Thread_2 는 UserManger.Test를 호출해서 _lock_user를 사용해서 Lock을 건다.

 

Thread_1은 SessionManger.Test를 통해 UserManger.TestUser를 호출할 때 _lock_user로 Lock을 걸어야 한다.

근데 Thread_2에서 _lock_user를 사용하고 있다보니까 Lock을 걸 수 없고

 

마찬가지로, Thread_2 역시 UserManger.Test를 통해 SessionManager.TestSession을 호출할 때
_lock_session으로 Lock을 걸어야 하는데

Thread_1에서 이미 사용하고 있다보니 Lock을 걸 수 없다 보니

 

데드락에 빠지게 된다.

 

 

 

 

 

번외로, 데드락이 나면 예외처리로 고치기 보다는 차라리 크래쉬를 내는 것이 더 좋다.

Lock 구조에 문제기 있어 데드락이 발생한거기 때문에 그 상황에서 예외처리를 하는 것은 장기적인 관점에서 좋지 않다. 훗날 어떤 예외가 튀어 나올지 모르기 대문이다.

 

또 데드락이 무서운 이유는 조금의 시간차가 있으면 발생하지 않을 수 있기 때문이다.

위 코드에서

t1.Start();

Thread.Sleep(100); 

t2.Start();

t1과 t2의 시작 시간을 다르게 해주면 코드가 정상적으로 작동하는 것을 볼 수 있다.

 

이것은 임시적인 방인이다

 

 

ref :https://velog.io/@kkuoo7/DeadLock

반응형
반응형

Monitor

이를 위해 Monitor 라는 기능이 존재한다.
사용하기 전 변수를 선언해주자.

static object _obj=new object();

Monitor는 다음과 같이 사용하면 된다.

Monitor.Enter(_obj);
//소스코드
Monitor.Exit(_obj);
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
    class Lock_Basic
    {
        //1. 전역변수 메모리 값을 레지스터에 갖고오고, 레지스터 값을 증가시키고, 그 값을 메모리 값에 넣는다
        static object _obj = new object();
        static int number = 0;
        static void Thread_1()
        {

            for (int i = 0; i < 10000; i++)
            {
                Monitor.Enter(_obj);
                number++;
                Monitor.Exit(_obj);
                
            }
        }
        static void Thread_2()
        {
            for (int i = 0; i < 10000; i++)
            {

                Monitor.Enter(_obj);
                number--;
                Monitor.Exit(_obj);

            }
        }
        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2);
            Console.WriteLine(number);
        }
    }
}

 

이와 같이 실행한다면 증감연산자는 원자적으로 동작하지 않음에도 불구하고 잘 작동하는 점을 확인할 수 있을 것이다.

아시다시피 동시 다발적으로 쓰레드가 공유 변수에 접근시 문제가 발생했고, 이를 우리는Critical Section(임계영역)이라고 첫 시간때 이야기를 하였다.

그러나 Monitor.Enter, Monitor.Exit을 통해 이를 방지하여 이 사이의 코드는 사실상 싱글 쓰레드와 같은 상태가 되어버렸다. 이러한 조치를 Mutual Exclusion(상호배제)라고도 설명하였다.

Enter, Exit은 쉽게 이야기하면 1인용 화장실과 같다.
Enter(화장실 들어가서 문잠금)를 통해 한 사람이 화장실을 이용하고 있으면 Exit(화장실을 다 쓰고 문 열기)하기 전까지 바깥의 사람들은 화장실을 사용할 수 없다.

적어도 그 순간만큼은 소스코드에 다른 쓰레드가 끼어들 껀덕지가 없다는 뜻이다.

하지만, Monitor의 경우에도 단점이 있는데, 만약 Exit를 선언하지 않게 된다면 어떻게 될까? 화장실은 계속 이용하고 있고, 영영 화장실을 나가지 않게 되면 바깥의 사람들은 주구장창 기다려야 할 것이다.

이러한 상황을 데드락이라고 하는데, 이는 다음 시간에 자세히 살펴볼 것이다.

따라서 Monitor를 쓰는 경우 모든 경우(예외 처리)까지 Exit을 고려해야 하는 골치아픈 단점이 있어 거의 대부분 쓰이지 않는다.

 

 

 

처럼 Enter, Exit으로 샌드위치처럼 코드를 감쌌지만

lock(_obj){
//소스코드
}

Lock에선 이와 같이 소스코드를 넣어주면, lock 구절이 끝난 이후엔 자동으로 Monitor.Exit을 하게 되는 효과를 가지게 된다.

 

 


lock은 thread-unsafe(안전하지 않은 스레드)코드를 안전하게 사용하고자 할때 사용한다. 즉, 동기화 처리를 할때 사용하는 구문이다.

그렇다면, thread-unsafe한 코드는 어떤 코드인가 다음 예제를 보자.

       static public class Division
        {
            static int num1 = 100, num2 = 5;

            static public void Divide()
            {
                try
                {
                    if (num2 != 0) Console.WriteLine(num1 / num2);
                    num2 = 0;
                }
                catch (Exception e)
                {
                    Debug.WriteLine(string.Format("Err : {0}", e));
                }
            }
        }

 

 

Divide 함수가 스레드 하나의 함수에 의해 호출되면 안전하게 동작하겠지만, 두개 이상의 스레드에서 동시에 실행되면 thread-unsafe한 상태가 된다.

첫번째 실행된 스레드가 num2 = 0으로 할당하는 시점에 두번째 실행된 스레드가  num1/num2를 수행하게되면 DivideByZeroException("0으로 나누려 했습니다.")을 발생시키면서 정상 동작을 못하는 상황이 발생될 수 있기때문이다.

thread-safe(스레드에 안전한 코드)로 만들려면 아래 클래스와 같이 수정해주면된다.

    static public class Division
    {
        static int num1 = 100, num2 = 5;
        static readonly object divisionlocker = new object();

        static public void Divide()
        {
            try
            {
                lock (divisionlocker)
                {
                   if (num2 != 0) Console.WriteLine(num1 / num2);
                    num2 = 0;
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(string.Format("Err : {0}",e));
            }
        }
    }

 

lock 구문에 의해 첫번째로 실행된 스레드의 코드 처리 위치가 lock 구문안에 있다면, 두번째로 실행된 스레드는 첫번째 실행된 스레드가 lock구문 밖으로 나올때까지 기다렸다가 실행하여 thread-safe하게 동작한다.

 

 

 

ref : https://velog.io/@tunadolphin/C%EA%B3%BC-%EC%9C%A0%EB%8B%88%ED%8B%B0%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-MMORPG-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C-%EC%8B%9C%EB%A6%AC%EC%A6%88-Part4-lock-%EA%B8%B0%EC%B4%88

ref : https://sixthman23.tistory.com/entry/lock-%EA%B7%B8%EB%A6%AC%EA%B3%A0-MonitorEnterMonitorExit

반응형
반응형
class Program
    {

        static int number = 0; 

        static void Thread_1()
        {
            for(int i=0; i<100000; i++)
            {
                number++; 
            }
        }

        static void Thread_2()
        {
            for(int i=0; i<100000; i++)
            {
                number--; 
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(number);
        }
   }

number에 1을 더하는 연산을 10만번, number에 1을 빼는 연산을 10만번 수행한다.

우리는 당연히 number에 0이 할당되었을 것이라 생각 할수 있지만 그렇지 않다

 

하지만 결과는 전혀 엉뚱한 값이 나온다.

이런 현상이 발생하는 이유는 Race Condition(경합 조건) 때문이다.

 

 

 

코드를 자세히 살펴 보자.

number++;

number--; 

두 코드는 어셈블리 언어의 관점에서 아래와 같이 해석할 수 있다.

다른 스레드
int temp = number; 

temp += 1; 

number = temp; 



다른 스레드
int temp = number; 

temp -= 1;

number = temp;

 


전역변수의 값을 다른 스레드에서 각각 서로 변경하려다보니 이전 값으로 덮어 쓰게 되어 문제가 발생함

 

이것은 가시성 문제가 아니다

 

 

이러한 문제를 해결하려면 원자성(Atomicity)을 보장해주면 된다.

원자성이란? 더 이상 쪼개질 수 없는 성질로써, 어떠한 작업이 실행될때 언제나 완전하게 진행되어 종료되거나, 그럴 수 없는 경우 실행을 하지 않는 경우를 말한다

즉, number++와 number-- 연산이 원자성을 보장하여 한꺼번에 실행되거나 아예 실행하지 않도록 해주면 된다.

 

 

static void Thread_1()
{
	for(int i=0; i<100000; i++)
    	{
    		Interlocked.Increment(ref number);
    	}
}

static void Thread_2()
{
	for(int i=0; i<100000; i++)
	{
    		Interlocked.Decrement(ref number); 
	}

Interlocked 계열은 All or Nothing. 즉, 실행을 하거나 안되거나 이다.

Interlocked 하나가 실행되면 다른 Interlocked은 대기하고 있는다.

이렇게 함으로써 동시다발적으로 경합을 하면 최종승자만이 작업을 할 수 있고 결과를 보장받을 수 있다.

하지만 작업 속도는 당연히 원래 연산보다 느려질 수 밖에 없다. 한 작업이 끝날 때 까지 다른 작업이 대기해야 하기 때문

 

 

번외로, 만약에 우리가 number란 값을 추출하고 싶다면 어떻게 해야할까?

for(int i=0; i<100000; i++)
{
	prev = number; 
	Interlocked.Increment(ref number);
	next = number; 
}
int afterValue = Interlocked.Increment(ref number);

위 코드와 같이 Interlocked 함수의 반환 값을 통해 number의 값을 추출해야 한다.

 

이렇게 바로 받아오는건 스레드 세이프한데

 

아래 처럼 처리하면 next = number; 에서 스레드 세이프 하지 않게 된다

이 순간다른 스레드가 값을 바꿀 수있다

for(int i=0; i<100000; i++)
{
	prev = number; 
	Interlocked.Increment(ref number);
	next = number; 
}
반응형

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

Deadlock 현상  (0) 2022.11.27
lock 과 Monitor 임계영역  (0) 2022.11.26
MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27
반응형

 

명령의 순서와 가시성에 도움을 주는 명령어 

최적화를 막아준다

 


Memory barrier

메모리 베리어는 membar, memory fence, fence instruction으로 알려져 있다. 메모리 배리어는 CPU나 컴파일러에게 barrier 명령문 전 후의 메모리 연산을 순서에 맞게 실행하도록 강제하는 기능이다. 즉 barrier 이전에 나온 연산들이 barrier 이후에 나온 연산보다 먼저 실행이 되는게 보장되어야 하는 것이다.

Memory barrier는 현대 CPU가 성능을 좋게 하기 위해 최적화를 거쳐 순서에 맞지 않게 실행시키는 결과를 초래할 수 있기 때문에 필수적이다. 이런 메모리 연산(load/store)의 재배치는 single 쓰레드로 실행할 때는 알아차리기 어렵지만, 제대로 관리되지 않는 한 병행적으로 작동되는 프로그램과 디바이스 드라이버에서 예상할 수 없는 결과를 초래하기도 한다. 싱글 쓰레드에서는 하드웨어가 명령어 통합성을 강제하기 때문에 싱글 쓰레드에서는 문제가 되지 않는다.

즉 정리하자면 성능을 좋게 만드려고 코드의 순서를 바꿔서 실행시킬 수 있는데, 이런 것을 막고 순서대로 코드가 실행되게 하도록 강제하는 것이다. 아래 간략화 된 코드를 보자. 실제로 동작하는 코드는 아니고, 핵심적인 부분만 남겨놓은 코드다.

static void Thread1() {
  y = 1; // Store y
  r1 = x; // Load x
}

static void Thread2() {
  x = 1; // Store x
  r2 = y; // Load y
}

static void Main(string[] args) {
  while(true) {
    x = y = r1 = r2 = 0; // 반복문의 초기부분마다 모든 변수의 값을 0으로 할당한다.
    
    Task t1 = new Task(Thread1);
    Task t2 = new Task(Thread2);
    
    t1.Start();
    t2.Start();
    
    Task.WaitAll(t1, t2);
    
    if (r1 == 0 & r2 == 0) 
      break;
  }
  System.out.println("While문이 break되었다.");
}

메모리 연산인 store과 load를 하는 Thread1, Thread2가 있고, 이를 Main 함수 안에서 실행시킨다. 위의 동작을 봤을 때 우리가 기대하는 것은 초기에 0으로 설정되어 있는 x, y 값이 1로 바뀌고 r1, r2에 이어서 1이 할당되면서 while 문이 계속 반복되어 무한 루프를 도는 것이다. 하지만 실제로 실행해 보면(위의 코드가 실행되는 건 아니다) While문이 break되었다. 로그가 찍히는 경우가 생긴다.

어떻게 이런 일이 가능한 것일까? 분명 while문을 빠져 나오는 조건은 r1과 r2가 모두 0일 때였다. 이 반복문을 빠져나왔다는 것은 원래 아래의 순서대로 실행되어야 할 코드가

x = 0 // 반복문 안
y = 0

x = 1 // 쓰레드 안
y = 1

r1 = x // 쓰레드 안
r2 = y

실제로는 아래와 같이 실행되는 것이다.

x = 0 // 반복문 안
y = 0

r1 = x // 쓰레드 안
r2 = y

// 여기에서 반복문 빠져나옴!

x = 1 // 쓰레드 안
y = 1

성능의 최적화를 위해서 코드의 순서를 이렇게 뒤집어서 실행시킬 수 있는데, 싱글 쓰레드에서는 문제가 되지 않지만 멀티 쓰레드에서는 문제가 된다. 따라서 이를 방지하기 위해, 명령어를 순서대로 실행시키기 위해 메모리 배리어를 사용한다.

static void Thread1() {
  y = 1; // Store y
  
  Thread.MemoryBarrier(); // 메모리 배리어
  
  r1 = x; // Load x
}

static void Thread2() {
  x = 1; // Store x
  
  Thread.MemoryBarrier(); // 메모리 배리어
  
  r2 = y; // Load y
}

static void Main(string[] args) {
  while(true) {
    x = y = r1 = r2 = 0; // 반복문의 초기부분마다 모든 변수의 값을 0으로 할당한다.
    
    Task t1 = new Task(Thread1);
    Task t2 = new Task(Thread2);
    
    t1.Start();
    t2.Start();
    
    Task.WaitAll(t1, t2);
    
    if (r1 == 0 & r2 == 0) 
      break;
  }
  System.out.println("While문이 break되었다.");
}

위의 코드와 같이 메모리 배리어를 설정하면 배리어 위의 코드가 배리어 아래의 코드와 순서가 바뀌어서 실행될 수 없게 된다. 이렇게 코드의 실행 순서를 보장해 주는 것이 메모리 배리어의 역할이다.

종류

메모리 배리어는 메모리 read/write가 내가 예상한 대로 실행되게 하는 명령어의 클래스다. 예를 들어 full fence(barrier)는 fence 전의 모든 read/write 는 fence 이후의 read/write 을 실행시키기 전에 모두 실행되어야 한다는 것이다. 메모리 배리어의 종류는 아래와 같다.

  1. Full Memory Barrier : read/write 를 둘 다 막는다.
  2. Store Memory Barrier : write만 막는다.
  3. Load Memory Barrier : read만 막는다.

메모리 배리어는 하드웨어 개념임을 기억해야 한다. 높은 레벨의 언어에서는 mutex와 세마포어를 이용해서 이를 해결한다. 낮은 레벨에서 메모리 배리어를 사용하고 메모리 배리어를 명시적으로 사용하는 것을 구현하는 것은 불필요할 수 있다. 메모리 배리어의 사용은 하드웨어 구조를 자세히 공부한 것이 전제가 되어야 하고, 애플리케이션 코드보다는 디바이스 드라이버에서 많이 사용된다.

 

 

 

 

 

 

 


메모리 배리어(Memory Barrier)

이 코드를 읽고 실행해보자.

 

 

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{

    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;


        static void Thread_1()
        {
            y = 1; // Store y
            r1 = x; // Load x 
        }

        static void Thread_2()
        {
            x = 1; // Store x 
            r2 = y; // Load y 
        }

        static void Main(string[] args)
        {
            int count = 0;
            while(true)
            {
                count++;
                x = y = r1 = r2 = 0;

                // t1, t2야 일해!
                Task t1 = new Task(Thread_1); 
                Task t2 = new Task(Thread_2);

                t1.Start();
                t2.Start();


                // t1 t2가 일이 끝날 때까지 Main Thread는 대기 
                Task.WaitAll(t1, t2);
                // t1,t2가 x,y,r1,r2를 모두 1로 바꾸어 주었으므로
                // 이론상으로는 무한반복 되어야 한다. 
                if (r1 == 0 && r2 == 0)
                    break;
            }

            Console.WriteLine($"{count}번 만에 r1, r2가 0이 되었습니다. ");

        }
    }
}

 

 

싱글쓰레드에서는 절대로 마지막 반복문을 빠져나올 수 없다.

하지만 생각보다 반복문을 잘 빠져나오는 것을 알 수 있다.


이는, 멀티쓰레드에서는 하드웨어 최적화가 적용되기 때문이다.

즉, 하드웨어가 쓰레드(Thread_1,Thread_2)에 준 연산들이 서로 상관이 없는 연산이라고 생각하면 의 연산 순서를 임의로 바꾸어서 연산하는 경우도 있기 때문이다.

이 때 메모리 배리어(Memory Barrier)를 사용한다.


메모리 배리어를 사용한 를 실행해보면 반복문을 빠져나오지 못한다.

 

메모리 베리어를 통해 코드 재배치 가시성을 확보 할 수 있다.

 

 

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{

    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;


        static void Thread_1()
        {
            y = 1; // Store y

            Thread.MemoryBarrier(); // notify other Thread stored 'y'
            r1 = x; // Load x 
        }

        static void Thread_2()
        {
            x = 1; // Store x 
            Thread.MemoryBarrier(); // notify other Thread stored 'x'
            r2 = y; // Load y 
        }

        static void Main(string[] args)
        {
            int count = 0;
            while(true)
            {
                count++;
                x = y = r1 = r2 = 0;

                // t1, t2야 일해!
                Task t1 = new Task(Thread_1); 
                Task t2 = new Task(Thread_2);

                t1.Start();
                t2.Start();


                // t1 t2가 일이 끝날 때까지 Main Thread는 대기 
                Task.WaitAll(t1, t2);

                // Memory Barrier로 코드 재배치와 가시성이 확보된 상태 
                if (r1 == 0 && r2 == 0)
                    break;
            }

            Console.WriteLine($"{count}번 만에 r1, r2가 0이 되었습니다. ");

        }
    }
}

 

 

 

 

 

 


 

 

ref : https://yoojin99.github.io/cs/Memory-Barrier/

반응형

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

lock 과 Monitor 임계영역  (0) 2022.11.26
Interlocked.Increment 과 Race Condition  (0) 2022.11.26
A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
반응형

 

아래와 같은 길이 있을때

녹색 : 시작점

빨간색 : 도착점

노란색 화살표가 가르키는 곳 : 또 다른 길

 

시작점에서 끝점까지 이동 할때 노란색 화살표가 가리키는 곳으로 가게 되면 더 먼 경로로 가게 된다는 것을 알 수 있는데

Astar 에선 거리 측정을 두가지로 한다

1 현재 플레이어 위치와 목표 지점

2 현재 플레이어 위치 기준 이동 누적 거리가 얼마나 되는가

 

1,2 이것을 합산하여 거리 비교 판단을 하게 되어 더 가까운 쪽을 우선적으로 거리를 선택하여 도착점 까지 가게 되어 최단 경로를 구하게 됩니다
(최단 경로는 종점에서 역으로 시작점으로 가면서 현재 점의 부모 위치인 역으로 올라가는 방식입니다)

즉 여기선 아래쪽의 최단 경로를 찾게 된다는 애기가 됩니다

 

 

알고리즘 처리는 우선순위 큐를 활용해 현재 후보 노드들 중에서 합산 거리 가장 가까운 노드 기준으로 계속 탐색을 하는 방식입니다

 

 

 

 

 

코드는 4방향과 8방향이 있는데

4방향은 거리 10, 14를 모두 1로 처리해놓으면 됩니다

using System;
using System.Collections.Generic;
using System.Text;

namespace Algorithm
{
	class Pos
	{
		public Pos(int y, int x) { Y = y; X = x; }
		public int Y;
		public int X;
	}

	class Player
	{
		public int PosY { get; private set; }
		public int PosX { get; private set; }
		Random _random = new Random();
		Board _board;

		enum Dir
		{
			Up = 0,
			Left = 1,
			Down = 2,
			Right = 3 
		}

		int _dir = (int)Dir.Up;
		List<Pos> _points = new List<Pos>();

		struct Node : IComparable<Node>
        {
			public int F;
			public int G;
			public int X;
			public int Y;

            int IComparable<Node>.CompareTo(Node other)
            {
				return F == other.F ?  0 : (F < other.F ? 1 : -1);
            }
        }

		public void Initialize(int posY, int posX, Board board)
		{
			PosY = posY;
			PosX = posX;
			_board = board;

			AStar();

		}

		void AStar()
        {
            //점수 매기기
            //F = G + H
            //F = 최종 점수 (작을 수록 좋음, 경로에 따라 달라짐)

            //G = 시작점에서 해당 좌표까지 이동하는데 드는 비용 ( 작을 수록 좋음, 경로에 따라 달라짐 ), 현재 노드에서 열린 다음 노드까지
            //H = 목적지에서 얼마나 가까운지 (작을 수록 좋음, 고정)

            //open (거리 계산용) 배열과
            //closed 이미 방문한 노드인지 파악하기 위한배열
            //이 둘의 배열이 각각 존재한다

            // open 한 x,y 위치에 목적지까지 거리가 얼마나 되는지 거리 계산 후 저장한다 그렇지 않은것들은 max distance,  H 와 유사
            //오픈 리스트에 있는 정보들 중에서, 가장 좋은 후보를 뽑기 위해 => 우선순위 큐를 쓴다 => 저장 되는 기준은 거리가 짭을 수록 순위가 높은 것

            //방문 하게 되면 closed 가 된다, 방문한 곳은 더이상 조사하지 않는다

            //이동할수 있는 노드라면 예약을 해 놓는다, 상 하 좌우 좌표 확인해서 예약 한다(open)

            //F = G(현재 노드에서 다음 노드까지 거리) + H(현재 노드에서 목적지 까지의 거리H)

            //open 된 것들 중에서  계산한 F 가 open 되어진 즉 이전에 계산했던 open 거리보다 보다 작다면 더이상 계산 안함
            //=> open 된것과 현재 노드 기준 사방향을 보면서 가장 적은 비용의 F 를 구해서 다시 open[nextX, nextY] = F 에 넣어 준다
            //그리고 이것을 우선순위 Q 에 넣어 준다


			//4방향일때 
            // 			int[] deltaX = new int[] { 0, -1, 0, 1 };
            // 			int[] deltaY = new int[] { -1, 0, 1, 0 };
            // 			int[] cost = new int[] { 1, 1, 1, 1 };

			//8방향 일때
            int[] deltaX = new int[] { -1, 0,  1, 0,		-1,  1,  1, -1 };
            int[] deltaY = new int[] {  0, -1, 0, 1,		-1, -1 , 1,  1 };
            int[] cost =   new int[] { 10, 10, 10, 10,	14, 14, 14, 14 };

            bool[,] closed = new bool[_board.Size, _board.Size];
			int[,] open = new int[_board.Size, _board.Size];

			Pos[,] parent = new Pos[_board.Size, _board.Size];

			for(int i=0;i<_board.Size;++i )
            {
                for (int j = 0; j < _board.Size; ++j)
                {
					open[i, j] = int.MaxValue;
                }
            }


			PriorityQueue<Node> pq = new PriorityQueue<Node>();

			//초기 H 
			open[PosY, PosX] =  10 *  Math.Abs(_board.DestX - PosX) + Math.Abs(_board.DestY - PosY);
			pq.Push(new Node() { F=  open[PosY, PosX], G=0, X = PosX, Y = PosY });
			parent[PosY, PosX] = new Pos(PosY, PosX);

			while(pq.Count > 0)
            {
				//현재 후보군들 중에서 목표와 가장 근접한(계산식상) 후보를 하나 뽑는다
				Node node = pq.Pop();

				//방문했던 노드는 건너띔
				if(closed[node.Y, node.X])
                {
					continue;
                }
				closed[node.Y, node.X] = true;
				if (node.X == _board.DestX && node.Y == _board.DestY)
                {
					break;
                }

				//4방향으로 이동 할수 있는 노드인지 확인해서 가능하다면 예약(open) 에 거리 계산 한다
				for (int i = 0; i < deltaY.Length; ++i)
				{
					int nextX = node.X + deltaX[i];
					int nextY = node.Y + deltaY[i];

					//테두리를 벗어나지 않은 경우 and  벽이 아니며 and 방문을 안했었다면 새로운 open 노드를 찾는다
					if ((nextX < 0 || nextX >= _board.Size || nextY < 0 || nextY >= _board.Size) ||	
						(_board.Tile[nextY, nextX] == Board.TileType.Wall) ||
						closed[nextY, nextX])
					{
						continue;
					}


					int g = node.G + cost[i];
					//예약 가능 다음 노드 후보들에 대한 거리 계산을 하고
					//목적지와 다음 노드 같의 거리 임으로 이 차이가 더 작은 곳으로 
					int h =  10 *  Math.Abs(_board.DestX - nextX) + Math.Abs(_board.DestY - nextY);		
					//이전에 했었던 거리 계산이 더 작다면 skip
					if(open[nextY, nextX] < g + h)
                    {
						continue;
                    }
					 
					//거리를 갱신한다 더 짧은 거리로
					open[nextY, nextX] = g + h;
					pq.Push(new Node() { F = g + h, G = g, X = nextX, Y = nextY });

					parent[nextY, nextX] = new Pos(node.Y, node.X);

				}



			}

			calculatePath(parent);
		}

		void calculatePath(Pos[,] parent)
        {
			int x = _board.DestX;
			int y = _board.DestY;
			while(parent[y,x].X != x || parent[y, x].Y != y)
            {
				_points.Add(new Pos(y, x));
				Pos pos = parent[y, x];
				x = pos.X;
				y = pos.Y;
			}
			_points.Add(new Pos(y, x));
			_points.Reverse();
		}
			

		

		const int MOVE_TICK = 10;
		int _sumTick = 0;
		int _lastIndex = 0;
		public void Update(int deltaTick)
		{
			if (_lastIndex >= _points.Count)
				return;

			_sumTick += deltaTick;
			if (_sumTick >= MOVE_TICK)
			{
				_sumTick = 0;

				PosY = _points[_lastIndex].Y;
				PosX = _points[_lastIndex].X;
				_lastIndex++;
			}
		}
	}
}

 

 

 

 

노란 블럭 : 이동 경로

녹색 : 플레이어

붉은색 : 종점

왼쪽 상단 : 시작점

 

 

 

단점 : 목표하는대상과 거리가 멀 수록 성능이 떨어진다 => 조사 해야 하는 주변 블락 들이 많아지게 됨으로

장점 : BFS 보다 주변 환경이 반영된 좀 더 빠른 길 찾기가 가능하다, BFS 는 조사하는 범위가 계속 커진다

astar 는 BFS 보다는 조사 범위가  타일 하나의  누적 이동 거리+  플레이어의 위치에서 목표지점까지의 거리

를 고려하게 됨으로 좀 덜 커질 수 있다

 

 

거리 찾는데 Astar 도 느린건 마찬가지다 그렇다면?

JPS : 대상지점까지 거리를 찾는건 Astar 와 유사한데 길을 찾을때 대상 목표지점과 플레이어 사이에 벽같은게 있다면 벽면의 외각 부분에 우선순위가 높아 그쪽을 먼저 향해 가는 방식으로 주변 모든 타일을 게속 탐색하면서 가는 방식보다 성능이 빠르다

 

반응형

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

Interlocked.Increment 과 Race Condition  (0) 2022.11.26
MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
반응형
using System;
using System.Collections.Generic;

namespace TreeHeight
{

    class TreeNode<T>
    {
        public T data { get; set; }
        public List<TreeNode<T>> children { get; set; } = new List<TreeNode<T>>();
    }

    class Program
    {

        static TreeNode<string> makeTree()
        {
            TreeNode<string> root = new TreeNode<string>() { data = "Root" };
            {
                TreeNode<string> node = new TreeNode<string>() { data = "left" };
                node.children.Add(new TreeNode<string>() { data = "cc" });
                root.children.Add(node);
            }
            {
                TreeNode<string> node = new TreeNode<string>() { data = "design" };
                node.children.Add(new TreeNode<string>() { data = "combat" });
                node.children.Add(new TreeNode<string>() { data = "econo" });
                node.children.Add(new TreeNode<string>() { data = "abc" });
                root.children.Add(node);
            }
            {
                TreeNode<string> node = new TreeNode<string>() { data = "right" };
                node.children.Add(new TreeNode<string>() { data = "bb" });
                root.children.Add(node);
            }
            return root;
        }

        static int getTreeheight(TreeNode<string> root)
        {
            int height = 0;
            foreach(var elem in root.children)
            {
                int treeHeight = getTreeheight(elem) + 1;
                if(treeHeight > height)
                {
                    height = treeHeight;
                }
            }

            return height;
        }

        static void Main(string[] args)
        {
            var tree = makeTree();
            int treeHeight = getTreeheight(tree);
            Console.WriteLine(treeHeight);

        }
    }
}

 

결과는 2

 

 

 

반응형

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

MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
A* (Astar) , JPS  (0) 2022.10.28
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
Comparer<T> Class  (0) 2018.10.15
반응형

Dijkstra 

 

다음과 같은 노드가 있다 가정하에 접근해봅니다

 

 

 

 

출발에서 A 로 갈때 미리 8을 a 까지드는 비용을 누적으로 구해놓고

출발 -> B -> A 로 가는 경로가 누적적으로 구해지면서 A에 도달했을때 기존 누적값과 누적해서 공통인 노드에 도달했을때 즉 A 에 도달했을때 누적된 것 중 더 빠른 거리를 경로로 선택하는 것

 

 

using System;

namespace GraphTraversal
{
    class Graph
    {
        // -1 은 연결 안된 상태를 표시
        int[,] adj = new int[6, 6]
        {
            { -1, 15, -1, 35, -1, -1 },
            { 15, -1, 5, 10, -1, -1 },
            { -1, 5, -1, -1, -1, -1 },
            { 35, 10, -1, -1, 5, -1 },
            { -1, -1, -1, 5, -1, 5 },
            { -1, -1, -1, -1, 5, -1 },
        };

        public void Dijikstra(int start)
        {
            bool[] visited = new bool[6];
            int[] distance = new int[6];
            Array.Fill(distance, Int32.MaxValue);
            int[] parent = new int[6];

            distance[start] = 0;        //자기자신은 0 거리로 만든다
            parent[start] = start;      //자기자신의 부모는 자기자신으로

            while (true)
            {
                // 제일 좋은 후보를 찾는다. 

                // 가장 유력한 정점의 거리와 번호를 저장한다.
                int closet = Int32.MaxValue;
                int now = -1;

                for (int i = 0; i < 6; i++)
                {
                    // 이미 방문한 정점은 스킵
                    if (visited[i])
                        continue;
                    // 아직 발견(예약)된 적이 없거나, 아직 방문하지 않고 거리만 계산한 노드 중에서 가장 짧은 노드를 찾아내기 위한 과정
                    if (distance[i] == Int32.MaxValue || distance[i] >= closet)
                        continue;

                    closet = distance[i];
                    now = i;
                }

                //now : 아직 방문하지 않은 노드 중 가장 짧은 거리가 짧은 노드
                if (now == -1) 
                    break;

                visited[now] = true;

                for (int next = 0; next < 6; next++)
                {
                    // 연결되지 않은 정점은 스킵한다
                    if (adj[now, next] == -1)
                        continue;

                    // 이미 방문한 정점은 스킵
                    if (visited[next])
                        continue;

                    //now : 1  ,
                    //next : 3
                    // 새로 조사된 정점의 최단 거리를 계산한다.
                    //                      15  +  10
                    int nextDist = distance[now] + adj[now, next];
                    // 이전까지 누적해서 계산 했던 노드 길이distance[next]보다,
                    // 현재 계산하고있는 누적 길이(nextDist)가 더 작다면 next Dist 즉 더 짧은 경로로 변경한다
                    if (nextDist < distance[next])
                    {
                        distance[next] = nextDist;      //해당 노드의 현재 까지 짧은 거리
                        parent[next] = now;             //해당 노드의 현재까지 짧은 거리의 부모

                        //각 노드에 누적된 거리가 계속 갱신 됨으로 
                    }
                }
            }
        }
    }
    class Program
    {

        static void Main(string[] args)
        {

            Graph dijik = new Graph();

            dijik.Dijikstra(0);

        }
    }
}

 

실행 결과

 

 

 벨만 포드 알고리즘과 다익스트라 알고리즘의 가장 큰 차이점은 벨만 포드 알고리즘은 방향 그래프에서 음의 가중치를 지닌 간선이 존재해도 사용할 수 있다 라는 점입니다. 따라서 음의 가중치를 가진 방향 그래프를 주면서 최단 거리를 구하라고 한다면 다익스트라 알고리즘이 아닌 벨만 포드 알고리즘을 사용 해야 합니다. 

 

 

 

 

ref : 한곳 더 있는데 url 을 잊어버림

ref : https://velog.io/@changhee09/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8B%A8%EA%B2%BD%EB%A1%9C-%EB%B2%A8%EB%A7%8C-%ED%8F%AC%EB%93%9C

https://www.youtube.com/watch?v=F-tkqjUiik0

반응형

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

A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
Comparer<T> Class  (0) 2018.10.15
out var  (0) 2018.09.09
반응형

출구 만들기

보드에 출구(목적지)를 표시해야 한다. Board 클래스에 DestY DestX 프로퍼티를 정의해서 렌더링 할 때 목적지를 노란색으로 표시하도록 코드를 수정한다.

 

class Board
{
    ...
    
    public int DestY { get; private set; }
    public int DestX { get; private set; }
    
    ...
    
    public void InitializeBoard(int size, Player player)
    {
        ...
        
        DestY = Size - 2;
        DestX = Size - 2;
        
        ...
    }
    
    public void Render()
    {
        ...
 
        for (int y = 0; y < Size; y++)
        {
            for (int x = 0; x < Size; x++)
            {
                ...
                
                else if (y == DestY && x == DestX)
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    
                ...
            } 
        }
    }
}

이제는 Board에 목적지 정보가 있으니 Player 쪽에서 목적지 정보를 받아올 필요가 없어졌다.

public void InitializePlayer(int posY, int posX, Board board)
{
    PosY = posY;
    PosX = posX;
 
    _board = board;
}

 


BFS 길찾기 알고리즘

인접 행렬 또는 인접 리스트 없이 타일 정보만 이용해서 가상의 그래프를 그려낼 수 있고 BFS 길 찾기 알고리즘을 적용할 수 있다. 하나의 타일을 노드라고 생각하고 상하좌우 바닥(노드)이 Empty라면 연결된 노드, Wall이라면 연결되지 않은 노드라고 판단할 수 있다.

 

BFS 코드를 작성하기 전에 미로 상의 X, Y좌표를 담을 Pos클래스를 정의했다.

class Pos
{
    public int Y;
    public int X;
 
    public Pos(int y, int x) { Y = y; X = x; }
}

 

※ BFS 코드 ※

여느 BFS 코드와 유사하다. 주목할 포인트는 parent path를 정의해서 찾아왔던 길을 기록하는 코드이다.

List<Pos> path = new List<Pos>();
 
...
 

   BFS();


...
 
private void BFS()
{
    int[] dirY = new int[] { -1, 0, 1, 0 };
    int[] dirX = new int[] { 0, -1, 0, 1 };
 
    bool[,] found = new bool[_board.Size, _board.Size];
    Pos[,] parent = new Pos[_board.Size, _board.Size];
 
    Queue<Pos> q = new Queue<Pos>();
    q.Enqueue(new Pos(PosY, PosX));
 
 
    found[PosY, PosX] = true;
    parent[PosY, PosX] = new Pos(PosY, PosX);
 
    while(q.Count > 0)
    {
        Pos pos = q.Dequeue();
 
        int nowY = pos.Y;
        int nowX = pos.X;
 
        for (int i = 0; i < 4; i++)
        {
            int nextY = nowY + dirY[i];
            int nextX = nowX + dirX[i];
 
            if (nextX <= 0 || nextX >= _board.Size || nextY <= 0 ||  nextY >= _board.Size)
                continue;
            if (_board.Tile[nextY, nextX] == Board.TileType.Wall)
                continue;
            if (found[nextY, nextX])
                continue;
 
            q.Enqueue(new Pos(nextY, nextX));
            found[nextY, nextX] = true;
            parent[nextY, nextX] = new Pos(nowY, nowX);
        }
    }
    
    CalcPathFromParent(parent);
}
 
private void CalcPathFromParent(Pos[,] parent)
{
    int y = _board.DestY;
    int x = _board.DestX;
 
    while (parent[y, x].Y != y || parent[y, x].X != x)
    {
        path.Add(new Pos(y, x));
 
        Pos pos = parent[y, x];
        y = pos.Y;
        x = pos.X;
    }
    path.Add(new Pos(y, x));
    path.Reverse();
}

 

 

BFS를 통해 path 정보를 채웠으니 Player가 자신의 위치를 갱신할 수 있도록 코드를 수정한다.

const int MOVE_TICK = 100;
private int _sumTick = 0;
int _index = 0;
 
public void Update(int deltaTick)
{
    if (_index >= path.Count)
        return;
 
    _sumTick += deltaTick;
 
    if (_sumTick >= MOVE_TICK)
    {
        _sumTick = 0;
 
        PosY = path[_index].Y;
        PosX = path[_index].X;
 
        _index++;
    }
}

 

똑똑한 Player로 진화했다.

 

 

ref : https://kangworld.tistory.com/57

반응형

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

Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
Comparer<T> Class  (0) 2018.10.15
out var  (0) 2018.09.09
dynamic 형식 사용  (0) 2018.09.08
반응형

IComparer<T> 제네릭 인터페이스의 구현에 대한 기본 클래스를 제공합니다.



[Serializable] public abstract class Comparer<T> : System.Collections.Generic.IComparer<T>, System.Collections.IComparer





예제

다음 예제에서는 파생 클래스 BoxLengthFirst는 Comparer<T> 로부터 상속 받은 클래스입니다. 

이 comparer 는 타입 Box 의 두 오브젝트를 비교합니다

먼저 길이에 의해 그다음, 높이, 너비에 의해 정렬됩니다. 


Box 클래스는 두 Box 오브젝트들 사이에서 기본 비교를 다루기위해  IComparable<T> 인터페이스를 구현합니다

이 기본 구현은 높이, 길이 그리고 넓이에 의해 정렬됩니다

예제는 차이점을 나타냅니다, 두 비교에서 이 비교는 Box오브젝트 List를 정렬하는 것 

첫번째로 BoxLengthFirst Comparer 를 사용하는 것 그리고 기본 comparer 를 사용하여 비교하는 것입니다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
using System;
using System.Collections;
using System.Collections.Generic;
 
class Program
{
    static void Main(string[] args)
    {
        List<Box> Boxes = new List<Box>();
        Boxes.Add(new Box(42014));
        Boxes.Add(new Box(121212));
        Boxes.Add(new Box(82010));
        Boxes.Add(new Box(6102));
        Boxes.Add(new Box(284));
        Boxes.Add(new Box(268));
        Boxes.Add(new Box(41220));
        Boxes.Add(new Box(18104));
        Boxes.Add(new Box(24418));
        Boxes.Add(new Box(10416));
        Boxes.Add(new Box(10210));
        Boxes.Add(new Box(6182));
        Boxes.Add(new Box(8124));
        Boxes.Add(new Box(12108));
        Boxes.Add(new Box(1466));
        Boxes.Add(new Box(16616));
        Boxes.Add(new Box(2812));
        Boxes.Add(new Box(4248));
        Boxes.Add(new Box(8620));
        Boxes.Add(new Box(181812));
 
        // Sort by an Comparer<T> implementation that sorts
        // first by the length.
        Boxes.Sort(new BoxLengthFirst());
 
        Console.WriteLine("H - L - W");
        Console.WriteLine("==========");
        foreach (Box bx in Boxes)
        {
            Console.WriteLine("{0}\t{1}\t{2}",
                bx.Height.ToString(), bx.Length.ToString(), 
                bx.Width.ToString());
        }
 
        Console.WriteLine();
        Console.WriteLine("H - L - W"); 
        Console.WriteLine("==========");
 
        // Get the default comparer that 
        // sorts first by the height.
        Comparer<Box> defComp = Comparer<Box>.Default;
 
        // Calling Boxes.Sort() with no parameter
        // is the same as calling Boxs.Sort(defComp)
        // because they are both using the default comparer.
        Boxes.Sort();
 
        foreach (Box bx in Boxes)
        {
            Console.WriteLine("{0}\t{1}\t{2}",
                bx.Height.ToString(), bx.Length.ToString(), 
                bx.Width.ToString());
        }
 
 
        // This explicit interface implementation
        // compares first by the length.
        // Returns -1 because the length of BoxA
        // is less than the length of BoxB.
        BoxLengthFirst LengthFirst = new BoxLengthFirst(); 
 
        Comparer<Box> bc = (Comparer<Box>) LengthFirst;
 
        Box BoxA = new Box(268);
        Box BoxB = new Box(101214);
        int x = LengthFirst.Compare(BoxA, BoxB);
        Console.WriteLine();
        Console.WriteLine(x.ToString());
 
    
 
    }
 
}
 
public class BoxLengthFirst : Comparer<Box> 
{
    // Compares by Length, Height, and Width.
    public override int Compare(Box x, Box y)
    {
        if (x.Length.CompareTo(y.Length) != 0)
        {
            return x.Length.CompareTo(y.Length);
        }
        else if (x.Height.CompareTo(y.Height) != 0)
        {
            return x.Height.CompareTo(y.Height);
        }
        else if (x.Width.CompareTo(y.Width) != 0)
        {
            return x.Width.CompareTo(y.Width);
        }
        else
        {
            return 0;
        }
    }
 
}
 
// This class is not demonstrated in the Main method
// and is provided only to show how to implement
// the interface. It is recommended to derive
// from Comparer<T> instead of implementing IComparer<T>.
public class BoxComp : IComparer<Box>
{
    // Compares by Height, Length, and Width.
    public int Compare(Box x, Box y)
    {
        if (x.Height.CompareTo(y.Height) != 0)
        {
            return x.Height.CompareTo(y.Height);
        }
        else if (x.Length.CompareTo(y.Length) != 0)
        {
            return x.Length.CompareTo(y.Length);
        }
        else if (x.Width.CompareTo(y.Width) != 0)
        {
            return x.Width.CompareTo(y.Width);
        }
        else
        {
            return 0;
        }
    }
}
 
public class Box : IComparable<Box>
{
 
    public Box(int h, int l, int w)
    {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; private set; }
    public int Length { get; private set; }
    public int Width { get; private set; }
 
    public int CompareTo(Box other)
    {
        // Compares Height, Length, and Width.
        if (this.Height.CompareTo(other.Height) != 0)
        {
            return this.Height.CompareTo(other.Height);
        }
        else if (this.Length.CompareTo(other.Length) != 0)
        {
            return this.Length.CompareTo(other.Length);
        }
        else if (this.Width.CompareTo(other.Width) != 0)
        {
            return this.Width.CompareTo(other.Width);
        }
        else
        {
            return 0;
        }
    }
 
}
 
 




icOMPARER<t> 인터페이스를 상속 받아 구현한 클래스 입니다, 그와 같은 GENERIC 클래스들은 다음과 같은 것들이 있습니다


SortedList<TKey,TValue> and SortedDictionary<TKey,TValue> generic classes.



Remarks

Derive from this class to provide a custom implementation of the IComparer<T> interface for use with collection classes such as the SortedList<TKey,TValue> and SortedDictionary<TKey,TValue> generic classes.

The difference between deriving from the Comparer<T> class and implementing the System.IComparable interface is as follows:

  • To specify how two objects should be compared by default, implement the System.IComparable interface in your class. This ensures that sort operations will use the default comparison code that you provided.

  • To define a comparer to use instead of the default comparer, derive from the Comparer<T> class. You can then use this comparer in sort operations that take a comparer as a parameter.

The object returned by the Default property uses the System.IComparable<T> generic interface (IComparable<T> in C#, IComparable(Of T) in Visual Basic) to compare two objects. If type T does not implement the System.IComparable<T> generic interface, the Default property returns a Comparer<T> that uses the System.IComparable interface.



ref : https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.comparer-1?view=netframework-4.7.2


반응형

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

다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
out var  (0) 2018.09.09
dynamic 형식 사용  (0) 2018.09.08
각종 Linq 함수들 Enumerable.Range, Where,Take, Any, Repeat, Distinct, Zip  (0) 2018.09.07
반응형


변환 할경우 변수를 toNum 처럼 만들어야 하는데 이를 좀 더 간결하게

out var 로 대체가 가능하다 C# 7.0 부터..


1
2
3
4
5
6
7
8
9
10
            int toNum;
            if(int.TryParse("1234"out toNum))
            {
                Console.WriteLine(toNum);
            }
 
            if(int.TryParse("1234"out var num))
            {
                Console.WriteLine(num);
            }

cs






out 인수를 사용하여 메서드 호출

C# 6 및 이전 버전에서는 out 인수로 전달하기 전에 별도 문에서 변수를 선언해야 합니다. 

다음 예제에서는 Int32.TryParse 메서드에 전달되기 전에 number라는 변수를 선언합니다. 

이 메서드는 문자열을 숫자로 변환하려고 합니다.

string numberAsString = "1640"; int number; if (Int32.TryParse(numberAsString, out number)) Console.WriteLine($"Converted '{numberAsString}' to {number}"); else Console.WriteLine($"Unable to convert '{numberAsString}'"); // The example displays the following output: // 결과 : Converted '1640' to 1640


C# 7.0부터 별도 변수 선언이 아니라 메서드 호출의 인수 목록에서 out 변수를 선언할 수 있습니다. 

이렇게 하면 보다 간결하고 읽기 쉬운 코드가 생성되며 메서드 호출 전에 실수로 
변수에 값이 할당되는 경우를 방지할 수 있습니다. 

다음 예제는 Int32.TryParse 메서드 호출에서 number 변수를 정의한다는 점을 제외하고 이전 예제와 비슷합니다.

string numberAsString = "1640"; if (Int32.TryParse(numberAsString, out int number)) Console.WriteLine($"Converted '{numberAsString}' to {number}"); else Console.WriteLine($"Unable to convert '{numberAsString}'"); // The example displays the following output: // 결과 : Converted '1640' to 1640




앞의 예제에서 number 변수는 int로 강력하게 형식화됩니다. 

다음 예제와 같이 암시적 형식 지역 변수를 선언할 수도 있습니다.


string numberAsString = "1640"; if (Int32.TryParse(numberAsString, out var number)) Console.WriteLine($"Converted '{numberAsString}' to {number}"); else Console.WriteLine($"Unable to convert '{numberAsString}'"); // The example displays the following output: // 결과 Converted '1640' to 1640




ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/out-parameter-modifier

반응형
반응형


런타임에서 타입 변환이 가능한 Dynamic 형식



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        static void Main(string[] args)
        {
 
            //컴파일 타임에 결정
            var s = "ddd";
 
            //런타임에 데이터 타입이 결정됨
            dynamic dd = 3;
 
            //런타임 중간에 다른 타입으로 변경 가능
            dd = "ddd";
            Console.WriteLine(dd);
 
 
            dd = 10;
 
            //컴파일 타임에 에러가 발생하진 않지만 런타임에서 예외를 발생시킨다
            //Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: '암시적으로 'int' 형식을 'string' 형식으로 변환할 수 없습니다.'
            s = dd;
 
            
        }

cs




dynamic 형식 사용

C# 4에서는 새로운 형식 dynamic을 소개합니다. 이 형식은 정적 형식이지만 dynamic 형식의 개체가 정적 형식 검사를 건너뜁니다.대부분의 경우 이 형식은 object 형식을 가지고 있는 것처럼 작동합니다. 컴파일 시간에 dynamic 형식의 요소는 모든 연산을 지원하는 것으로 간주됩니다. 따라서 개체가 값을 COM API, IronPython 같은 동적 언어, HTML DOM(문서 개체 모델), 리플렉션 또는 프로그램의 다른 곳 등 어디서 가져오든 신경을 쓸 필요가 없습니다. 그러나 코드가 유효하지 않으면 런타임 시 오류가 catch됩니다.

예를 들어 다음 코드의 인스턴스 메서드 exampleMethod1에 매개 변수가 하나뿐인 경우, 컴파일러는 메서드 ec.exampleMethod1(10, 4)에 대한 첫 번째 호출이 유효하지 않음을 인식합니다. 여기에 인수가 두 개 포함되었기 때문입니다. 호출 시 컴파일러 오류가 발생합니다. 컴파일러는 메서드 dynamic_ec.exampleMethod1(10, 4)에 대한 두 번째 호출을 확인하지 않습니다. dynamic_ec의 형식이 dynamic이기 때문입니다. 따라서 컴파일러 오류가 보고되지 않습니다. 그러나 이 오류는 알림을 무기한 이스케이프하지 않고, 런타임에 catch되며 런타임 예외를 일으킵니다.


static void Main(string[] args)
{
    ExampleClass ec = new ExampleClass();
    // The following call to exampleMethod1 causes a compiler error 
    // if exampleMethod1 has only one parameter. Uncomment the line
    // to see the error.
    //ec.exampleMethod1(10, 4);

    dynamic dynamic_ec = new ExampleClass();
    // The following line is not identified as an error by the
    // compiler, but it causes a run-time exception.
    dynamic_ec.exampleMethod1(10, 4);

    // The following calls also do not cause compiler errors, whether 
    // appropriate methods exist or not.
    dynamic_ec.someMethod("some argument", 7, null);
    dynamic_ec.nonexistentMethod();}
class ExampleClass
{
    public ExampleClass() { }
    public ExampleClass(int v) { }

    public void exampleMethod1(int i) { }

    public void exampleMethod2(string str) { }
}

이 예제에서 컴파일러의 역할은 dynamic으로 형식이 지정된 개체 또는 식에 대해 각 문이 해야 할 일에 대한 정보를 패키지하는 것입니다. 런타임에는 저장된 정보의 검사가 수행되며, 유효하지 않은 문에서 런타임 예외가 발생합니다.

대부분의 동적 작업은 결과 그 자체가 dynamic입니다. 다음 예제에서 testSum이 사용된 곳에 마우스 포인터를 올려두면 IntelliSense에서 (지역 변수) dynamic testSum 형식을 표시합니다.


dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);

결과가 dynamic이 아닌 작업은 다음을 포함합니다.

  • dynamic에서 다른 형식으로의 전환.
  • dynamic 형식의 인수를 포함하는 생성자 호출.

예를 들어 다음 선언에서 testInstance의 형식은 dynamic이 아니라 ExampleClass입니다.


var testInstance = new ExampleClass(d);

변환 예제는 다음 섹션인 "변환"에 나와 있습니다.

변환

동적 개체와 다른 형식 간에 손쉽게 변환할 수 있습니다. 따라서 개발자는 동적 동작과 비동적 동작 간에 전환할 수 있습니다.

다음 예제와 같이 개체를 동적 형식으로 암시적으로 변환할 수 있습니다.


dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();

반대로, 암시적 변환을 dynamic 형식의 식에 동적으로 적용할 수 있습니다.

int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;

동적 형식의 인수로 오버로드 확인

메서드 호출 내 하나 이상의 인수에 dynamic 형식이 있거나 메서드 호출의 수신자가 dynamic 형식인 경우 오버로드 확인은 컴파일 시간이 아니라 런타임에 발생합니다. 다음 예제에서 액세스 가능한 유일한 exampleMethod2 메서드가 문자열 인수를 사용하도록 정의되는 경우, d1을 인수로서 전송하면 컴파일러 오류는 발생하지 않지만 런타임 예외가 발생합니다. d1의 런타임 형식은 int인데 exampleMethod2에는 문자열이 필요하므로 오버로드 확인이 런타임에 실패합니다.


// Valid.
ec.exampleMethod2("a string");

// The following statement does not cause a compiler error, even though ec is not
// dynamic. A run-time exception is raised because the run-time type of d1 is int.
ec.exampleMethod2(d1);
// The following statement does cause a compiler error.
//ec.exampleMethod2(7);

동적 언어 런타임

DLR(동적 언어 런타임)은 .NET Framework 4의 새로운 API입니다. DLR은 C#에서 dynamic 형식을 지원하는 인프라를 제공하며, IronPython 및 IronRuby와 같은 동적 프로그래밍 언어를 구현합니다. DLR에 대한 자세한 내용은 동적 언어 런타임 개요를 참조하세요.

COM Interop

C# 4에는 Office 자동화 API와 같은 COM API와의 상호 운용 환경을 개선하는 몇 가지 기능이 포함되어 있습니다. 개선 사항 중에는 dynamic 형식의 사용 및 명명된 인수 및 선택적 인수의 사용이 포함됩니다.

많은 COM 메서드는 형식을 object로 지정하여 인수 형식 및 반환 형식의 변환을 허용합니다. C#에서는 강력한 형식의 변수로 조정하기 위해 값을 명시적으로 캐스팅해야 했습니다. /link(C# 컴파일러 옵션) 옵션을 사용하여 컴파일하는 경우 dynamic 형식을 사용하면 COM 서명에서 object의 발생을 마치 dynamic 형식인 것처럼 취급하여 캐스팅을 상당 부분 피할 수 있습니다. 예를 들어 다음 문은 dynamic 형식은 있고 dynamic 형식은 없는 Microsoft Office Excel 스프레드시트의 셀에 액세스하는 방법과 대조됩니다.


// Before the introduction of dynamic.
((Excel.Range)excelApp.Cells[1, 1]).Value2 = "Name";
Excel.Range range2008 = (Excel.Range)excelApp.Cells[1, 1];




// After the introduction of dynamic, the access to the Value property and
// the conversion to Excel.Range are handled by the run-time COM binder.
excelApp.Cells[1, 1].Value = "Name";
Excel.Range range2010 = excelApp.Cells[1, 1];




ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/types/using-type-dynamic




반응형
반응형



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
 
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace TestProject
{
 
    class t1s
    {
 
        static void Main(string[] args)
        {
            //지정된 된 범위 내의 정수 시퀀스를 생성 합니다.
            //Enumerable.Range 시작숫자부터 개수까지 숫자를 만드는 linq 함수
            int[] arr =  Enumerable.Range(110).Select(x => x * x).ToArray();
 
 
 
            foreach (var item in arr.Where(num => num % 2 == 1))  //linq 를 통해 배열에 확장 메소드 Where 를 사용 할 수 있다
            {
                Console.WriteLine(item);
            }
 
            Console.WriteLine("");
 
            foreach (var item in arr.Where(num => num % 2 == 1).Take(3))  //Take 는 앞에서부터 가져올 개수를 지정해준다
            {
                Console.Write(item + " " );
            }
            Console.WriteLine("");
 
 
            Console.WriteLine(   arr.Any(num => num == 1));     //Any : 값이 하나라도 존재하는가? , linq 에 있는 확장 메소드
 
            Console.WriteLine("");
 
 
 
            //3을 5번 생성
            int[] arr2 = Enumerable.Repeat(35).ToArray();
 
            foreach (var item in arr2)
            {
                Console.Write(item);
            }
 
 
            Console.WriteLine("");
            arr2[1]=5;
            arr2[3= 5;
            //중복된 값을 하나로 (중복 제거)
            foreach (var item in arr2.Distinct())
            {
                Console.Write(item+" ");
            }
 
 
            Console.WriteLine("\n\narr ");
            foreach (var item in arr)
            {
                Console.Write(item + " ");
            }
            Console.WriteLine("\n\narr2");
            foreach (var item in arr2)
            {
                Console.Write(item + " ");
            }
            Console.WriteLine("\n");
 
            //zip 두개의 컨테이너를 돌면서 원소가 양쪽 모두 존재할때까지 두 원소를 합친 컨테이너를 리턴
            var t3 = arr.Zip(arr2, (first, second) => "[ " + first + " : " + second + " ]" );
 
            Console.WriteLine("");
 
            foreach (var item in t3)
            {
                Console.Write(item + ",  ");
            }
        }
 
    }
}
 
 
 

cs



결과 


1

9

25

49

81


1 9 25

True


33333

3 5


arr

1 4 9 16 25 36 49 64 81 100


arr2

3 5 3 5 3



[ 1 : 3 ],  [ 4 : 5 ],  [ 9 : 3 ],  [ 16 : 5 ],  [ 25 : 3 ],



반응형
반응형

익명 함수


익명 함수는 대리자 형식이 예상되는 곳에서 항상 사용할 수 있는 “인라인” 문 또는 식입니다. 

이를 사용하여 명명된 대리자를 초기화하거나 명명된 대리자 형식 대신 이를 메서드 매개 변수로 전달할 수 있습니다.

익명 함수에는 두 가지가 있고 각각 다음 항목에서 설명합니다.


C#에서 대리자의 발전


C# 1.0에서는 코드의 다른 위치에 정의된 메서드를 사용하여 명시적으로 초기화하는 방식으로 대리자의 인스턴스를 만들었습니다. 


C# 2.0에서는 대리자 호출에서 실행될 수 있는 이름 없는 인라인 문 블록을 작성하는 방법으로 무명 메서드의 개념을 소개했습니다. 


C# 3.0에서는 개념적으로 무명 메서드와 비슷하지만 더 간결하고 표현이 다양한 람다 식을 소개했습니다. 


이러한 두 기능을 함께 익명 함수라고 합니다. 


일반적으로 .NET Framework의 버전 3.5 이상을 대상으로 하는 응용 프로그램은 람다 식을 사용해야 합니다.



다음 예제에서는 C# 1.0에서 C# 3.0까지 대리자 만들기의 발전을 보여 줍니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Test
{
    delegate void TestDelegate(string s);
    static void M(string s)
    {
        Console.WriteLine(s);
    }
 
    static void Main(string[] args)
    {
        // Original delegate syntax required 
        // initialization with a named method.
        TestDelegate testDelA = new TestDelegate(M);
 
        // C# 2.0: A delegate can be initialized with
        // inline code, called an "anonymous method." This
        // method takes a string as an input parameter.
        TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };


//C# 3.0. 델리게이트가 람다 표현에 의해 초기화 될 수 있고 또한 람다는
//string 입력 파라미터를 받을 수 있는데 x 의 타입은 컴파일러에 의해서 추론됩니다.
        // C# 3.0. A delegate can be initialized with
        // a lambda expression. The lambda also takes a string
        // as an input parameter (x). The type of x is inferred by the compiler.
        TestDelegate testDelC = (x) => { Console.WriteLine(x); };
 
        // Invoke the delegates.
        testDelA("Hello. My name is M and I write lines.");
        testDelB("That's nothing. I'm anonymous and ");
        testDelC("I'm a famous author.");
 
        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Hello. My name is M and I write lines.
    That's nothing. I'm anonymous and
    I'm a famous author.
    Press any key to exit.
 */

cs




ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions

반응형
반응형


컴파일 타임에 확장 메서드 바인딩

확장 메서드를 사용하여 클래스 또는 인터페이스를 확장할 수 있지만 재정의할 수는 없습니다. 
(확장 메소드 자체가 static 으로 정의됨)

이름과 시그니처가 인터페이스 또는 클래스 메서드와 동일한 확장 메서드는 호출되지 않습니다. 

컴파일 시간에 확장 메서드는 항상 형식 자체에서 정의된 인스턴스 메서드보다 우선 순위가 낮습니다. 

즉, 형식에 Process(int i)라는 메서드가 있고 동일한 시그니처를 가진 확장 메서드가 있는 경우 컴파일러는 항상 인스턴스 메서드에 바인딩합니다. 컴파일러는 메서드 호출을 발견할 경우 먼저 형식의 인스턴스 메서드에서 일치 항목을 찾습니다. 일치 항목이 없으면 형식에 대해 정의된 확장 메서드를 검색하고 찾은 첫 번째 확장 메서드에 바인딩합니다. 다음 예제에서는 컴파일러가 바인딩할 확장명 메서드 또는 인스턴스 메서드를 확인하는 방법을 보여 줍니다.

다음 예제에서는 C# 컴파일러가 메서드 호출을 형식의 인스턴스 메서드 또는 확장명 메서드에 바인딩할 것인지 결정할 때 따르는 규칙을 보여 줍니다. 정적 클래스 Extensions는 IMyInterface를 구현하는 모든 형식에 대해 정의된 확장 메서드를 포함합니다. AB및 C 클래스는 모두 인터페이스를 구현합니다.

MethodB 확장 메서드는 이름과 시그니처가 클래스에서 이미 구현된 메서드와 정확하게 일치하므로 호출되지 않습니다.

일치하는 시그니처를 가진 인스턴스 메서드를 찾을 수 없으면 컴파일러는 일치하는 확장명 메서드(있는 경우)에 바인딩합니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;
 
    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}
 
 
// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;
 
    // The following extension methods can be accessed by instances of any 
    // class that implements IMyInterface.
    
    //확장 메소드를 만들기 위해선 확장 메소드가 포함되는 클래스에 static 을 붙여줘야 한다
    public static class Extension
    {
 
        //각각 IMyInterface 에 대한 확장 메소드 MethodA 에 대하여 정의한다
 
        //확장 메소드의 경우 this 가 붙는데 this 다음에 넘겨줄 타입을 정해줘야한다
        //확장 메소드 또한 오버로드가 가능하다
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
        }
 
        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
        }
 
        //이 함수는 호출되지 않는데 왜냐면 각 A,B,C 3개 클래스는 시그니처에 매칭되는 MethodB를 구현하기 때문이다
        // This method is never called in ExtensionMethodsDemo1, because each 
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}
 
 
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;
 
    //각 클래스는 확장 메소드에 의해 확장된 IMyInterface 인터페이스를 상속 받는다
    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }             //MethodB 구현 정의함
    }
 
    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }             //MethodB 구현 정의함
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }
 
    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }             //MethodB 구현 정의함
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }
 
 
 
 
    public  class DemoClass
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.
            //      
            A a = new A();
            B b = new B();
            C c = new C();
 
            
 
            // A contains no MethodA, so each call to MethodA resolves to 
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(IMyInterface, int)
            a.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 
            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()
 
            Console.WriteLine("");
 
            // B has methods that match the signatures of the following
            // method calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()
 
            // B has no matching method for the following call, but 
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 
 
            Console.WriteLine("");
 
 
            //C의 경우에는 확장 메소드MethodA가 있어도 C클래스에서 object 에 대한 함수를 정의해놨기 때문에
            //클래스(C)의 MethodA 가 호출되게 된다
            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
 

cs



결과


Extension.MethodA(this IMyInterface myInterface, int i)

Extension.MethodA(this IMyInterface myInterface, string s)

A.MethodB()


B.MethodA(int i)

B.MethodB()

Extension.MethodA(this IMyInterface myInterface, string s)


C.MethodA(object obj)

C.MethodA(object obj)

C.MethodB()



일반 지침

일반적으로 반드시 필요한 경우에만 드물게 확장 메서드를 구현하는 것이 좋습니다. 

가능하면 기존 형식을 확장해야 하는 클라이언트 코드는 기존 형식에서 파생된 새 형식을 만들어 이 작업을 수행해야 합니다. 
자세한 내용은 상속을 참조하세요.

기존 메서드를 사용하여 소스 코드를 변경할 수 없는 형식을 확장하는 경우 형식의 구현이 변경되어 확장명 메서드가 손상될 수도 있습니다.

지정된 형식에 대해 확장 메서드를 구현하는 경우 다음 사항에 유의하세요.

  • 시그니처가 형식에 정의된 메서드와 동일한 확장 메서드는 호출되지 않습니다.

  • 확장 메서드는 네임스페이스 수준에서 범위로 가져옵니다. 
    예를 들어 Extensions라는 단일 네임스페이스에 확장 메서드를 포함하는 여러 개의 정적 클래스가 있는 경우 using Extensions; 지시문을 통해 모두 범위로 가져옵니다.


구현된 클래스 라이브러리의 경우 어셈블리의 버전 번호가 증가되는 것을 방지하기 위해 확장 메서드를 사용해서는 안 됩니다. 소스 코드를 소유하고 있는 라이브러리에 중요 기능을 추가하려는 경우 어셈블리 버전 관리를 위한 표준 .NET Framework 지침을 따라야 합니다. 자세한 내용은 어셈블리 버전 관리를 참조하세요.



ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/extension-methods



반응형
반응형


확장명 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다. 확장 메서드는 특수한 종류의 정적 메서드이지만 확장 형식의 인스턴스 메서드인 것처럼 호출됩니다. C#, F# 및 Visual Basic에서 작성된 클라이언트 코드의 경우 확장명 메서드를 호출하는 것과 형식에 실제로 정의된 메서드를 호출하는 데는 명백한 차이가 없습니다.


다음 예제에서는 정수 배열에서 표준 쿼리 연산자 OrderBy를 호출하는 방법을 보여 줍니다. 괄호 안의 식은 람다 식입니다. 많은 표준 쿼리 연산자가 람다 식을 매개 변수로 사용하지만 확장명 메서드에 대한 요구 사항은 아닙니다. 자세한 내용은 람다 식을 참조하세요.



가장 일반적인 확장명 메서드는 쿼리 기능을 존 System.Collections.IEnumerable 및 

System.Collections.Generic.IEnumerable<T>  형식에 추가하는 LINQ 표준 쿼리 연산자입니다.


표준 쿼리 연산자를 사용하려면 using System.Linq 지시문을 사용해서 먼저 범위를 지정합니다. 

그러면 IEnumerable<T>을 구현하는 모든 형식에 GroupByOrderByAverage 등의 인스턴스 메서드가 있는 것처럼 나타납니다. 


List<T> 또는 Array와 같은 IEnumerable<T> 형식의 인스턴스 뒤에 "dot"를 입력하면 IntelliSense 문 완성에서 

이러한 추가 메서드를 볼 수 있습니다.



//상단에 아래 구문 필요

using System.Linq;


class ExtensionMethods2 { static void Main() { int[] ints = { 10, 45, 15, 39, 21, 26 }; var result = ints.OrderBy(g => g); foreach (var i in result) { System.Console.Write(i + " "); } } } //Output: 10 15 21 26 39 45


확장명 메서드는 정적 메서드로 정의되지만 인스턴스 메서드 구문을 사용하여 호출됩니다. 확장 메서드의 첫 번째 매개 변수는 메서드가 작동하는 형식을 지정하며 매개 변수 앞에 this 한정자가 있습니다. 확장 메서드는 using 지시문을 사용하여 명시적으로 네임스페이스를 소스 코드로 가져오는 경우에만 범위에 있습니다.

다음 예제에서는 System.String 클래스에 대해 정의된 확장 메서드를 보여 줍니다. 이 확장 메서드는 제네릭이 아닌 비중첩 정적 클래스 내부에서 정의됩니다.


namespace ExtensionMethods { public static class MyExtensions { public static int WordCount(this String str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } }


WordCount 지시문을 사용하여 using 확장 메서드를 범위로 가져올 수 있습니다.

using ExtensionMethods;


또한 다음 구문을 사용하여 응용 프로그램에서 확장 메서드를 호출할 수 있습니다.

string s = "Hello Extension Methods"; int i = s.WordCount();


코드에서 인스턴스 메서드 구문을 사용하여 확장 메서드를 호출합니다. 그러나 컴파일러에서 생성된 IL(중간 언어)이 코드를 정적 메서드 호출로 변환합니다. 따라서 실제로 캡슐화의 원칙을 위반하지 않습니다. 사실상 확장명 메서드는 확장하는 형식의 private 변수에 액세스할 수 없습니다.

자세한 내용은 방법: 사용자 지정 확장 메서드 구현 및 호출을 참조하세요.

일반적으로 확장명 메서드를 직접 구현하는 것보다 호출하는 경우가 훨씬 많습니다. 확장 메서드는 인스턴스 메서드 구문을 사용하여 호출되므로 특별한 지식이 없어도 클라이언트 코드에서 확장 메서드를 사용할 수 있습니다. 특정 형식의 확장 메서드를 사용하려면 해당 메서드가 정의된 네임스페이스에 대해 using 지시문을 추가합니다. 예를 들어 표준 쿼리 연산자를 사용하려면 다음 using 지시문을 코드에 추가합니다.

using System.Linq;

System.Core.dll에 대한 참조를 추가해야 할 수도 있습니다. 이제 표준 쿼리 연산자가 대부분의 IEnumerable<T> 형식에 사용할 수 있는 추가 메서드로 IntelliSense에 표시됩니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using MyNamespace;
using System;
 
 
namespace MyNamespace
{
    public static class MyClass
    {
 
        //확장메서드를 사용할 경우 인자에 this 를 써주야 한다
        public static int WordCount(this String str)
        {
            //return str.Split(' ').Length;
            return str.Split(new char[] { ' ''.''?' },
                StringSplitOptions.RemoveEmptyEntries       //공백은 무시
                ).Length;
        }
    }
}
 
class DemoClass
{
    static void Main(string[] args)
    {
        string ss="sdf sdf";
        Console.WriteLine(ss.WordCount());
    }
 
}
 

cs

결과 : 2


ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/extension-methods




반응형
반응형
널 조건부 연산자 ( Evlis[엘비스] 연산자 )

대상?. 연산자는 대상이 null 이면 null 을 리턴하고

대상이 존재하면 대상.멤버 의 값을 리턴하는 연산자입니다


아래는 이에 관한 간단한 예입니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        static void Main(string[] args)
        {
 
            List<int> list = new List<int>();
            list.Add(10);
            //List<int> list = null;
 
            int? numbers = list?.Count;
 
            Console.WriteLine(numbers);   //1
            
        }
 
        static void Main(string[] args)
        {
            //List<int> list = new List<int>();
            //list.Add(10);
            List<int> list = null;
            int? numbers = list?.Count;
            Console.WriteLine(numbers);   //null
            
        }

cs




아래 코드는  null 병합 연산자 ?? 와 혼용한 형태인데

?? 연산자의 경우 ?? 앞의 값이 null 이면 ?? 의 뒤에 값을 리턴하고

?? 앞에 있는 값이 null 이 아니면 앞의 값을 그대로 사용합니다


1
2
3
4
5
6
7
8
9
10
11
        List<string> list = null;
 
        /*
        list = new List<string>();
        list.Add("0");
        list.Add("1");
        list.Add("2");*/
 
        int len = list?.Count ?? -1;
 
        Console.WriteLine(len);
cs


결과 : -1

주석을 해지하면 결과는 3


반응형

+ Recent posts