반응형

C# : using문의 기능과 활용

알게 된 배경

 C++에서 개발을 하다가 C#을 접하게 되었을 때 익숙하면서도 생소한 문법이 using문이었다. C++에서도 namespace를 선언할 경우 'using namespace [이름];' 형태의 문법을 사용하고 C#에서도 마찬가지로 최상단에 'using System;' 형태로 기본적으로 작성이 되어 있기 때문이다.


 하지만, 입문을 제대로 하지 않은 상태에서 파일, 네트워크 등 IO(입출력)에 해당되는 MSDN 문서를 보면 코드의 메서드 안에서 사용되는 using문을 쉽게 볼 수 있다. 하지만, 기존에 using 사용되던 키워드(?)와 다른 기능이라서 처음에 쉽게 사용하기 어렵다.


 특히나 예외처리 문법의 finally 문의 기능과 유사했기 때문에 필자의 경우 예제를 보기 전까지는 제대로 활용을 하지 못 했었다.


using문의 의미

 키보드와 콘솔 화면 출력을 제외한 대부분의 IO(입출력)의 경우 실제로 stream이나 unmanagement 메모리 공간을 할당하고 해제 하는 패턴 혹은 Stream Open 과 Stream Close 하는 패턴으로 코드를 작성해야 한다.


 C++의 경우 소멸자가 유용하게 사용되는데, C# 역시 소멸자를 제공하지만, IDisposeable 인터패이스를 상속하여 Dispose라는 메서드를 override하여 Unmanagement 메모리 해제나 Stream Close를 구현하도록 권장을 하고 있다. 그리고 이렇게 구현된 Dispose 메서드는 using문의 Scope에서 벗어나면, 호출이 하도록 하여 자연스럽게 C++의 소멸자와 비슷한 기능을 수행한다.


using문 예시


using System;
using System.IO;

class FileRead
{
    var imgFilePath = @".\img.jpg";
    using(var imgFileStream = new FileStream(imgFilePath, FileMode.Open))
    {
        var tmpImg = Image.FromStream(imgFileStream);
        // ... 중략
    }
}



예외처리의 finally문과의 관계

 IO처리에 있어서 항상 염두해야 하는 문법은 예외 처리이다. 이는 파일 입출력의 경우 파일을 읽을 때 파일이 없는 경우, 통신중에 네트워크 연결이 끊어진 경우 등 비정상적인 동작을 대비해서 작성하는 문법이다.


 여기서 finally 문의 경우 예외가 발생하던 안하던 간에 반드시 실행되는 내용을 작성을 하도록 되어 있다. 가령 Stream Open 한뒤에 작업중에 예외가 발생해서 파일을 닫아야 하지만, 예외 발생 하지 않은 경우에도 닫아야 하는 경우에 사용된다.


try-catch-finally문 예시

using System;
using System.IO;

class FileRead
{
    var imgFilePath = @".\img.jpg";
    try
    {
        var imgFileStream = new FileStream(imgFilePath, FileMode.Open);
        var tmpImg = Image.FromStream(imgFileStream);
        // ... 중략
    }
    catch(Exception ex)
    {
        // ... 예외 처리
    }
    finally
    {
        imgFileStream.Close();
        // ... 혹은 imgFileStream.Dispose() 호출
    }
}




 여기서 생각을 가만히 해보면 finally문이 using문과 역할의 유사점을 느낄 수 있다. 오히려 finally문의 경우 Close 같은 메서드를 직접 호출하는 반면, using문은 IDisposeable을 상속 받은 객체의 경우 자동으로 Scope를 벗어나면 Dispose() 메서드를 호출한다.


 그리고 MS의 대부분 Stream에 관련된 클래스들은 Dispose() 메서드에 이러한 기능들이 정의 되어 있다. 따라서 IO 구현시 가능하면 using문을 사용하는 것이 실수를 예방하기에 좋은 편이다.



using문/try-catch 패턴 예시


using System;
using System.IO;

class FileRead
{
    var imgFilePath = @".\img.jpg";
    using(var imgFileStream = new FileStream(imgFilePath, FileMode.Open))
    {
        try
        {
            var tmpImg = Image.FromStream(imgFileStream);
            // ... 중략
        }
        catch(Exception ex)
        {
            // ... 예외 처리
        }
    }
}


 위의 패턴으로만 보았을 때 using문-try-catch 패턴이 깔끔해보인다. 하지만, 처음 using문에서 사용할 객체를 만드는 순간에 발생하는 예외의 경우 처리가 안되는 패턴이기 때문에 이를 피하기 위해서 파일 존재 여부 검사와 같은 추가 코드가 필요하다.



참고자료

도서: C# 7과 닷넷 코어 2.0


반응형

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

C# .NET: Dispose (소멸자) 패턴  (0) 2018.05.14
C# .NET : SQLLocalDB (1) 개요와 오류사례  (0) 2018.03.23
C# : this와 base  (0) 2018.02.20
C# Winform : Detect Resize (크기 변화 감지)  (0) 2018.02.07
C# : Queue VS ConcurrentQueue  (0) 2018.02.02
반응형

C# .NET: Dispose (소멸자) 패턴

알게된 배경

 클래스가 프로그래밍 언어에서 등장할 때 가장 먼저 배우는 것이 생성자이다. 이후 생성자의 반대 개념인 소멸자(혹은 파괴자, 제거자 등 여러 이름으로 부를 수 있는 개념)개념이다.

 이는 기존 C++ 같은 언어에서 힙(요즘에는 이 힙을 자유공간(Free Space)라고 부르자고 제안되고 있음)에 할당된 메모리를 관리(할당과 해제)를하기 위해서였다.


 그러나 시간이 지나고 나서 GC(Garbage Collection)가 등장하고 나서 부터는 소멸자의 존재 의미가 애매 해졌다. C# DotNet Core 에서부터는 소멸자가 사라지게 되었지만, 기존의 DotNet FrameWork의 경우 아직도 소멸자가 건재하다.


 DotNet FrameWork에서는 메모리 관리 방법이 Management와 Unmanagement로 2가지 방법이 있다. Management는 말그대로 CLR이 관리를 해준다(이는 GC의 동작도 포함). 반면, Unmanagement는 CLR이 관여를 하지 않는다. 이렇게 Unmanagement한 영역은 Stream을 사용하는 부분과 소켓 프로그래밍을 위한 버퍼 등이 포함된다. 이러한 영역은 개발자가 직접 해제하는 코드를 작성해줘야 하는데, 이럴때 사용되는 것이 소멸자였다.


 그러나 DotNet FrameWork에서는 Dispose라는 인터페이스를 제공을 해주고 있는데, 이 인터페이스는 GC가 해당 Class의 인스턴스를 제거할 때 해당 작업을 먼저 하도록 되어 있다. 문제는 앞에서 언급된 Dispose 인터페이스와 소멸자의 역할이 유사하다 보니 오작동 될 가능성이 있다. 때문에 이러한 패턴이 생겨나게 되었다.


 Dispose 메서드는 IDisposeable 인터페이스를 상속 받아서 구현하면, using() 으로 호출하면 해당 스코프(혹은 스택)을 벗어나면, 호출이 된다.



패턴 예제



using System.Net.Sockets;

class UdpListenClass : IDisposeable
{
    private UdpClient Listener { get; private set; }
    // .. 중략
    // Start Destroyer Pattern
    private bool _disposed = false;
    private void Dispose(bool calledDestroyer)
    {
        if(false == _disposed)
        {
            // 소켓을 닫거나 Unmamagement 메모리를 여기서 해제하면 된다
            Listener.Close();
            _disposed = true;
        }
        if(false == calledDestroyer)
        {
            GC.SuppressFinalize(this);
        }
    }
    public void Dispose()
    {
        Dispose(false);
    }
    ~UdpSocket()
    {
        Dispose(true);
    }
    // End Destroyer Pattern
    // ... 중략
}


 혹시나 오해가 될까 추가로 적자면, 소멸자는 ~[클래스명]으로 선언 정의 된다. Dispose는 소멸자가 아니지만, using문 패턴에서 호출되는 인터패이스다. 그리고 최근에 나오고 있는 DotNet Core에서는 소멸자를 더 이상 사용하지 않는다.


 물론 DotNet FrameWork랑 독립적으로 업데이트가 되고 있지만, 새로운 기능을 2017년 8월부터 DotNet Core에 먼저 추가가 되고 이후에 DotNet FrameWork에 추가 할 것이라고 MS에서 발표를 했으니 참조만 하자


참고 자료

도서: 시작하세요. C# 6.0

도서: C# 7과 닷넷 코어 2.0

반응형

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

C# : using문의 기능과 활용  (0) 2018.05.15
C# .NET : SQLLocalDB (1) 개요와 오류사례  (0) 2018.03.23
C# : this와 base  (0) 2018.02.20
C# Winform : Detect Resize (크기 변화 감지)  (0) 2018.02.07
C# : Queue VS ConcurrentQueue  (0) 2018.02.02
반응형

Xamarin.Form : 빠른 시작(첫 앱 만들기)

작성 배경

 Xamarin.Form가 나온지 시간이 괘 지났음에도 한글문서가 많지 않은 것같다. 사실 튜토리얼에 있는 영문문서가 읽기에 어려운 편은 아니다. 그러나 단의 영어로 되어 있다는 점때문에 진입장벽이 생기는 것 같아서 틈틈히 정리를 한다. 물론 정리하다가 중단하게 될 수 도 있으리라...


 본문의 내용은 Xamarin.form 공식 웹페이지의 Getting Started -> Hello, Xamarin.Form -> part 1: Quickstart를 약간의 번역과 의역을 첨가하여 작성한다.



Xamain.Forms 빠른 시작


프로젝트 만들기

새프로젝트 항목 중 Visual C# 하위 목록중에서 Cross-platform를 선택을 하면 우측 괄호에 Xamarin.Form이 보이는 항목을 클릭한다.


프로젝트 이름은 "Phoneword" 로 한다.


실제 여러개의 프로젝트가 하나의 앱을 만듣는 것이기 때문에 가급적 솔루션용 폴더를 체크하도록 한다.


확인 버튼을 클릭을 하면, 크로스플래폼 앱에 대해서 세부 설정을 할 수 있다. "Blank App"을 선택하고 .NET Standard를 선택을 하고 프로젝트를 생성한다.


(먼저 Androd 프로젝트의 MainActivity.cs 파일을 열어보고 에러 메시지가 뜨는지 확인을 해야한다. Xamarin.Form이 메이저 버전이 업데이트 되면서 일부 호환이 되지 않아서 에러가 나는 경우가 있는데, 이때는 미련없이 재설치를 하자.)


여러개의 프로젝트중에서 Phoneword 프로젝트내의 MainPage.xaml을 열고 다음과 같이 수정한다.


<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                   x:Class="Phoneword.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="20, 40, 20, 20" />
            <On Platform="Android, WinPhone, Windows" Value="20" />
        </OnPlatform>
    </ContentPage.Padding>
    <StackLayout>
      <Label Text="Enter a Phoneword:" />
      <Entry x:Name="phoneNumberText" Text="1-855-XAMARIN" />
      <Button x:Name="translateButon" Text="Translate" Clicked="OnTranslate" />
      <Button x:Name="callButton" Text="Call" IsEnabled="false" Clicked="OnCall" />
    </StackLayout>
</ContentPage>


변경이 끝나면, 저장(Ctrl + s)을 한다.


이제 솔루션 탐색기(Solution Explorer)의 (+)버튼을 누르면 같은 이름의 MainPage.xaml.cs 파일이 보인다(혹은 소스코드 열기로 확인할 수 있음). 해당 파일을 수정한다.


 단, 주의 해야 할점이 에러가 발생하는 코드가 있는데, 이는 진행하면서 해결하니 조급해하지 말자.


MainPage.xaml.cs 입력받은 숫자문자를 숫자로 변환을 해주는 기능이다.


using System;
using Xamarin.Forms;

namespace Phoneword
{
    public partial class MainPage : ContentPage
    {
        string translatedNumber;

        public MainPage ()
        {
            InitializeComponent ();
        }

        void OnTranslate (object sender, EventArgs e)
        {
            translatedNumber = Core.PhonewordTranslator.ToNumber (phoneNumberText.Text);
            if (!string.IsNullOrWhiteSpace (translatedNumber)) {
                callButton.IsEnabled = true;
                callButton.Text = "Call " + translatedNumber;
            } else {
                callButton.IsEnabled = false;
                callButton.Text = "Call";
            }
        }

        async void OnCall (object sender, EventArgs e)
        {
            if (await this.DisplayAlert (
                    "Dial a Number",
                    "Would you like to call " + translatedNumber + "?",
                    "Yes",
                    "No")) {
                var dialer = DependencyService.Get<IDialer> ();
                if (dialer != null)
                    dialer.Dial (translatedNumber);
            }
        }
    }
}


에러 메시지에 겁먹지 말고 Ctrl + s 를 클릭하여 변경사항을 저장한다.


 같은 솔루션 탐색기에서 같은 프로젝트의 App.xaml 하위에 있는 App.xaml.cs를 열고 아래와 같이 수정을 한다.


using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Phoneword
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
            MainPage = new MainPage();
        }

        protected override void OnStart()
        {
            // Handle when your app starts
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
        }
    }
}


역시 변경사항을 저장한다.


솔루션 탐색기에서 Phoneword 프로젝트에서 마우스 우클릭을 하여 추가(Add)에서 새항목(New Item)을 선택한다.


 Visual C#의 code 항목에서 클래스(Class)를 선택하고 이름을 "PhoneTranslator"로 작성하고 추가(Add)한다.


생성된 PhoneTranslator.cs를 다음과 같이 수정한다.

(이때 주의 할 점이 namespace 이름을 바꾼다는 것을 간과 하지 말자)


using System.Text;

namespace Core
{
    public static class PhonewordTranslator
    {
        public static string ToNumber(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw))
                return null;

            raw = raw.ToUpperInvariant();

            var newNumber = new StringBuilder();
            foreach (var c in raw)
            {
                if (" -0123456789".Contains(c))
                    newNumber.Append(c);
                else
                {
                    var result = TranslateToNumber(c);
                    if (result != null)
                        newNumber.Append(result);
                    // Bad character?
                    else
                        return null;
                }
            }
            return newNumber.ToString();
        }

        static bool Contains(this string keyString, char c)
        {
            return keyString.IndexOf(c) >= 0;
        }

        static readonly string[] digits = {
            "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"
        };

        static int? TranslateToNumber(char c)
        {
            for (int i = 0; i < digits.Length; i++)
            {
                if (digits[i].Contains(c))
                    return 2 + i;
            }
            return null;
        }
    }
}


작성이 완료되었으면, 역시 저장한다.


Phoneword 프로젝트에 이번에는 Interface를 추가한다. 방법은 방금전과 마찬가지로 솔루션 탐색기에서 Phoneword 프로젝트에서 "우클릭 >> 추가 >> 새 항목" Visual C#의 code 인터페이스(interface)를 선택한뒤 IDialer로 이름을 적고 생성한다.


코드는 다음과 같이 수정한다.


namespace Phoneword
{
    public interface IDialer
    {
        bool Dial(string number);
    }
}


이 코드는 나중에 다른 프로젝트들에서 상속한다. 역시 저장을 한다.



이제, Dialer를 상속하자. 솔루션 탐색기에서 Phoneword.iOS 프로젝트에 우클릭을 한뒤에 "추가"에서 "새 항목"을 선택한다.


 새 항목 화면에서 Apple의 하위의 code를 선택하고, 클래스(Class)를 선택한다. 이때 클래스가 여러개 보이는데, 처음 보이는 것을 선택하면 된다.


 클래스 명은 "PhoneDialer"로 하여 생성한다.


코드는 다음과 같이 수정한다.


using Foundation;
using Phoneword.iOS;
using UIKit;
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.iOS
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)
        {
            return UIApplication.SharedApplication.OpenUrl (
                new NSUrl ("tel:" + number));
        }
    }
}


변경이 완료되었으면, 저장을 한다.


이번에는 안드로이드 쪽이다. Phoneword.Android 프로젝트에 우클릭 하고 추가의 새 항목을 선택한다.


추가 창에서 visual C#하위의 Android를 선택한뒤 클래스(Class)를 선택하여 이름을 아까와 동일한 PhoneDialer로 작성하고 생성한다.


아래와 같이 코드를 수정한다(이때 임포트 되는 모듈이 많으므로 빼먹지 않도록 주의한다).


using Android.Content;
using Android.Telephony;
using Phoneword.Droid;
using System.Linq;
using Xamarin.Forms;
using Uri = Android.Net.Uri;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.Droid
{
    public class PhoneDialer : IDialer
    {
        public bool Dial(string number)
        {
            var context = MainActivity.Instance;
            if (context == null)
                return false;

            var intent = new Intent (Intent.ActionCall);
            intent.SetData (Uri.Parse ("tel:" + number));

            if (IsIntentAvailable (context, intent)) {
                context.StartActivity (intent);
                return true;
            }

            return false;
        }

        public static bool IsIntentAvailable(Context context, Intent intent)
        {
            var packageManager = context.PackageManager;

            var list = packageManager.QueryIntentServices (intent, 0)
                .Union (packageManager.QueryIntentActivities (intent, 0));

            if (list.Any ())
                return true;

            var manager = TelephonyManager.FromContext (context);
            return manager.PhoneType != PhoneType.None;
        }
    }
}


변경이 완료되었으면 저장을 한다.



솔루션 탐색기에서 Phoneword.Android 프로젝트를 더블 클릭하여 MainActivity.cs 파일을 열어서 다음과 같이 수정한다(원문은 더블클릭하라고 했지만, 해당되는 이름의 파일을 클릭해도 된다).


using Android.App;
using Android.Content.PM;
using Android.OS;

namespace Phoneword.Droid
{
    [Activity(Label = "Phoneword", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        internal static MainActivity Instance { get; private set; }

        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            Instance = this;
            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());
        }
    }
}


작성이 완료되면 저장을 한다.


Phoneword.Android 프로젝트를 우클릭하여 속성을 선택한다. 속성에서 "Android 매니페스트"(Android Manifest)항목을 선택하고 하단의 Required permissions: 항목중 "CALL_PHONE"항목을 체크한다.


역시 변경되었으면 저장한다.



이제 UWP를 수정하자.

솔루션 탐색기에서 Phoneword.UWP 프로젝트에 우클릭을 한뒤 추가(Add)에 새 항목(New Item)을 선택한다. 선택화면에서는 Visual C# 에서 Code를 선택하여 클래스(Class)를 선택한다. 이름은 PhoneDialer로 작성하여 생성한다.


생성된 클래스는 다음과 같이 작성한다(아직 어셈블리를 추가하지 않았기 때문에 에러가 보이는 경우도 있으나 조급할 필요가 없다).


using Phoneword.UWP;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Calls;
using Windows.UI.Popups;
using Xamarin.Forms;

[assembly: Dependency(typeof(PhoneDialer))]
namespace Phoneword.UWP
{
    public class PhoneDialer : IDialer
    {
        bool dialled = false;

        public bool Dial(string number)
        {
            DialNumber(number);
            return dialled;
        }

        async Task DialNumber(string number)
        {
            var phoneLine = await GetDefaultPhoneLineAsync();
            if (phoneLine != null)
            {
                phoneLine.Dial(number, number);
                dialled = true;
            }
            else
            {
                var dialog = new MessageDialog("No line found to place the call");
                await dialog.ShowAsync();
                dialled = false;
            }
        }

        async Task<PhoneLine> GetDefaultPhoneLineAsync()
        {
            var phoneCallStore = await PhoneCallManager.RequestStoreAsync();
            var lineId = await phoneCallStore.GetDefaultLineAsync();
            return await PhoneLine.FromIdAsync(lineId);
        }
    }
}


변경이 완료되었으면 저장한다.


 프로젝트 탐색기에서 참조(Reference)를 우클릭한뒤 "참조 추가"(Add Reference)를 클릭한다. 

좌특의 Universal Windows의 하위 확장(Extensions)을 클릭한다.


 항목들중 Windows Mobile Extensions for the UWP 모듈 최신(작성시점 10.0.162..)을 체크하고, 확인 버튼을 클릭한다.

(종종 체크가 이미 되어 있는 경우가 있는데 이 때는 변경할 것이 없다.)



Phoneword.UWP 프로젝트 하위의 "Package.appxmanifest" 항목을 연다.

상위 탭버튼 중에 "기능"(Capabilities)를 선택한뒤에 좌측 기능중 "전화 통화"(Phone Call)를 체크 하고 저장한다.



이제 빌드할 시간이다. 먼저 지금까지 작성한 코드중에 에러가 있는지 확인을 해본다. 종종 IDailer를 찾지 못한다는 에러가 보일 수 있는데 이 경우 Phoneword 프로젝트를 빌드를 해주면 된다. Phoneword 프로젝트에 우클릭하여 빌드를 클릭하여 빌드하자.


Phoneword 빌드가 끝나면, 솔루션 탐색기에서 Phoneword.UWP 프로젝트를 우클릭을 한뒤 "시작프로젝트로 설정"(Set as StartUp Project)를 클릭한다. 이제 상단의 녹색화살표가 있는 "로컬 컴퓨터"(Local Machine)를 클릭한다(혹은 단축키 F5).


기다리면, UWP로 빌드된 프로그램이 실행된것을 확인할 수 있다.



참조자료

자바린 공식 페이지(영문)



반응형
반응형

C# : this와 base

정리배경

 무래도 C++을 사용하다가 C#을 접하게 되었기 때문에 유사한 키워드가 많아서 간과 하게 되는 부분들이 많다. 특히 this의 경우 친숙한 키위드이기 때문에 처음 공부를 할 때 스킵을 했다가 간단하면서 유용한 추가 기능이 있다는 것을 나중에 알게 되었다. base는 Java의 super와 같은 개념이나 이 역시 친숙한 기능은 아니였다.


 이러한 키워드들은 자주 사용하기 때문에 잊을 리는 없지만 정리 차원에서 작성해 놓는다.



this 키워드

 정의 된 클래스 내의 맴버(member)를 가르킬때 사용이 된다. 이러한 역할은 기존의 C++의 this와 동일한 기능이다.

 덤으로 코드 스타일은 프로젝트를 진행하는 팀별로 차이가 있지만, 맴버 객체 이름을 정할때 언더바("_")을 붙이거나 소문자 엠(m)을 붙이는 경우들이 있다.


예시1

class Book
{
    decimal isbn;
    public Book(decimal isbn)
    {
        this.isbn = isbn;
    }
}




 다른 기능은 클래스 내의 생성자에서 다른 생성자를 호출할때 사용된다. 일반적으로 생성자의 스코프(Scope)안에서 다른 생성자 호출을 되지 않지만, 생성자명 정의 직후 스코프전에 this 키워드를 사용하여 동일한 클래스내의 생성자를 호출하여 입력값을 전달하는게 가능해진다.


예시2

class Book
{
    decimal isbn;
    string title;
    string author;

    public Book(decimal isbn, string title)
        : this(isbn, title, string.Empty)
    {
    }

    public Book(decimal isbn, string title, string author)
    {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
    }

}




base 키워드

 Java의 super와 같은 키워드로 상속시 부모생성자에 입력값을 전달해야 할 경우 사용이 가능하다.


예시3

// 부모(parent)
class Book
{
    decimal isbn;
    public Book(decimal isbn)
    {
        this.isbn = isbn;
    }
}

// 자식(child)
class EBook : Book
{
    public EBook(decimal isbn)
        : base(isbn)
    {
    }
}




 C#의 base는 생성자 뿐만아니라 부모의 메서드를 호출 할 경우에도 사용이 가능하다. 이러한 기능은 후에 winform 등 override하여 기존 기능을 유지하면서 확장해야 할 경우에도 사용한다.

예시4

// 부모(parent)
public class Computer
{
    bool powerOn;
    public void Boot()
    {
        powerOn = true;
    }
}

// 자식(child)
public class Notebook : Computer
{
    public void ScanFinger()
    {
        base.Boot();
    }
}




참조자료

시작하세요 C#프로그래밍(서적)

반응형
반응형

C# Winform : Detect Resize (크기 변화 감지)

알게된 배경

 운영체제 별로 화면의 화소 사이즈가 다르며, 또 사용자에 따라서 편한 크기가 있다. 이 때문에 동적으로 크기를 바꿔줘야 한다. 때문에 창의 크기가 편할때 이를 감지를 하는 것은 제품을 만들기 위해서 중요한 문제일 수 있다.


 물론 이쁘고 동적인 UI를 만든다면, 가능하면, WPF를 쓰는 방법도 현명한 선택이 될 수 있다.



소스코드

 참조한 문서에 의하면, 별로 안어렵다고 한다. 기존에 크기 변화를 감지하는 메서드를 override를 하여서 추가 시킬 수 있다. 아래의 소스코드는 생성된 초기 디자인에서 코드보기를 한뒤에 작성하였다.


// Proect name = TestResize
using System;
using System.Windows.Forms;
namespace TestResize
{
    public partial class Form1 : Form
    {
        public ResizeForm()
        {
            InitializeComponent();
            mLastState = this.WindowState;
        }

        private FormWindowState mLastState;

        protected override void OnClientSizeChanged(EventArgs e)
        {
            // 폼의 크기가 바뀔때마다 인식
            if (this.WindowState != mLastState)
            {
                // 최대, 최소, 보통 등 상태가 변할때 마다 실행됨
                mLastState = this.WindowState;
                OnWindowStateChanged(e);
            }
            else
            {
                Console.WriteLine("Detect resize: {0}, {1}", Size.Width, Size.Height);
            }
            base.OnClientSizeChanged(e);
        }

        protected void OnWindowStateChanged(EventArgs e)
        {
            Console.WriteLine("Window State: {0}", WindowState);
        }
    }
}



빌드로 실행하기 전에 프로젝트 속성의 옵션 항목중에 "응용 프로그램" --> "출력형식" --> "콘솔 응용프로그램"으로 해야 콘솔창을 통해 코드가 작동이 되는지 확인할 수 있다.



참조자료

MSDN 포럼(영문)



반응형

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

C# .NET : SQLLocalDB (1) 개요와 오류사례  (0) 2018.03.23
C# : this와 base  (0) 2018.02.20
C# : Queue VS ConcurrentQueue  (0) 2018.02.02
C# : 닷넷에서 Broadcast UDP를 보내기(1)  (0) 2018.01.29
C# : const vs readonly  (0) 2017.10.25
반응형

C# : Queue VS ConcurrentQueue

알게된 배경

UDP통신을 수신해야 하는데, 스레드 하나로는 프로그램의 성능을 올릴 수 없으니 당연히 멀티 스레드로 처리를 해야한다. (이는 UDP 프로토콜은 오버플러워되거나 에러가 난 패킷은 가차없이 폐기 처분하기 때문에 발생되는 문제를 막기 위함이다.) 문제는 이런 스레드가 작동되는 영역들간에 데이터 공유혹은 전달 할 수 있는 자료가 필요로 하게 된다. 이를 사용할 자료구조가 Queue였다.

 물론 링크리스트로도 충분히 구현을 할 수 도 있다.


큐의 개요

 많이 비교되는 자료구조는 스택이다. 스택은 먼저들어간게 나중에 나오는 구조(FILO or LIFO)인데 반해 큐는 들어간 순서대로 나오는 실제 세게에서 줄 서 있는 것과 같다. 추상적으로 생각을 하면, 우리가 일상중 머릿속에서 작업스케줄을 짜는 것과 같다.

 위의 예시처럼 실제로도 큐는 작업 스케줄 처럼 많이 사용되기도 한다. 큐의 종류는 선 큐, 환 큐, 링크드 큐로 분류가 되는데, 선 큐의 경우 오버플러워의 위험이 크기 때문에 잘 사용되지 않는다. 닷넷 한정으로 넣는 것을 Enqueue, 빼는 것을 Dequeue로 구현이 되어 있다.


닷넷 Queue 구조

 닷넷에서 제공하는 큐(Queue)는 환 큐에 속한다. 또한 성능을 향상시키기 위해서 정적 배열을 기반으로 하고 있다.

문제점

Queue가 비어 있을 경우 Dequeue를 시도하면 예외를 발생시킨다. 그래서 일반적으로 큐를 사용할 때 Count를 검사해서 0 이상일 때 Dequeue 사용하도록 구현을 한다. 다만, 설계시 멀티스레드에 대해서 생각을 못 한 것인지 다수의 스레드의 동시 접근시 문제가 발생된다. 때문에 Enqueue와 Dequeue에 크리티컬 섹션으로 설정해야 한다(lock을 구현해야함).

 이론상 2개의 스레드중 하나는 Enqueue만하고 다른 하나는 Dequeue만 하면, 문제가 없을 것 같지만, count등 내부적으로 공유되는 값으로 인해 간헐적인 값의 오류가 발생한다. 이러한 오류는 에러를 잘 발생시키지는 않지만, 의도한것과 다른 값을 결과로 내기 때문에 위험하다.

Queue 클래스

 Queue<T>와 달리 Queue클래스가 추가로 존재하는데, 기능이 Queue<T>보다는 많지만, 동일한 문제점이 있다.


닷넷 ConcurrentQueue 구조

 닷넷 4.0 부터 지원하는 구조이다. 기존 Queue 구조의 멀티스레드로 인한 문제가 있어서 이러한 단점을 해결하기 위한 구조이다. 기존 Queue와 큰 차이점은 인스턴스화 할때 크기를 선언하지 않는다. 이때문에 링크드 큐인것으로 생각된다.

 Enqueue는 기존 Queue와 비슷하게 사용되지만, Dequeue 대신에 TryDequeue를 사용해서 자료를 반환한다. 메서드 이름에서 알 수 있듯이 Enqueue과정이 Dequeue보다 우선 순위가 높게 구현이 되어있다. 또한 Dequeue과정이 성공/실패 여부를 반환한다. 이를 통해서 Count에 접근할 필요가 없어졌다.


유니티에서 ConcurrentQueue

 유니티에서는 닷넷을 사용할 경우 2.0에 해당되기 때문에 공식적으로 ConcurrentQueue를 사용할 수 없다. lock을 활용하여 크리티컬 섹션을 잘 설정하자.


참조 자료

스택오버 플로워

MSDN Queue

MSDN ConcurrentQueue



반응형

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

C# : this와 base  (0) 2018.02.20
C# Winform : Detect Resize (크기 변화 감지)  (0) 2018.02.07
C# : 닷넷에서 Broadcast UDP를 보내기(1)  (0) 2018.01.29
C# : const vs readonly  (0) 2017.10.25
C# 싱글톤 패턴 상속용 클래스  (0) 2017.09.29
반응형

C# : 닷넷에서 Broadcast UDP를 보내기(1)

알게된 배경

회사에서 임베이드 분야로 소켓 프로그램을 만들어야 할 일이 있었다. 물론 디바이스 단이 아니라 클라이언트 단의 프로그램이다. 임베이드 분야는 통신을 주로 실시간으로 해야 하며, 단거리 통신을 많이 하기 때문에 TCP 통신보다는 UDP 통신을 많이 사용한다. 특히 동시에 여러대의 디바이스에 신호를 주기 가장 적합한 방식은 Broadcast UDP 방식이다.


Broadcast UDP 란?

이름대로 특정망에 연결된 기기들에게 거의 동시에 동일한 UDP신호를 정해진 포트로 보내는 방식이다. 여기서 거의 동시라는 건 라즈베리 파이같은 기기로 테스트해 본 결과 0.01초 내외의 오차가 있다. 이는 디바이스의 상태와 성능에 따라 조금씩 차이가 있는 것으로 보인다.


Broadcast UDP 원리

사실 원리라고 말하기는 애매하고 규약이라 보는 것이 나을 것이다. 규약조건은 IPv4 기준으로 망을 제공하는 네트워크의 서브 마스크에서 허용되는 IPv4 주소의 비트가 1이 되게 보내면 해당신호는 Broadcast로 인식이 된다. 따라서 Broadcast UDP를 보내기 위해서는 해당 네트워크 망의 서브넷 마스크와 네트워크 제공 IP주소 체계를 알아야 한다.


[보조 설명] 서븐넷 마스크와 Broadcast UDP

서브넷 마스크 주소체계의 고정값과 비고정값을 구분하는 기준이라고 생각하면 된다. 마스크라는 단어의 의미 자체가 가면 혹은 가린다는 뜻이다. 네트워크의 신호는 주로 비트로 표현이 되는데, 마스크의 비트가 1일 경우 해당 값은 가려지는 것이고, 반대로 0 이 되는 부분은 가려지지 않은 값이다.

즉, 255.255.255.0 이라고 할경우 255는 비트상 모두 1이 되어서 255가 되는 부분은 가려져서 내부망 주소에서 구분을 하지 않는다. 반면 0인 부분은 8비트를 가리지 않아서 이론상 0~255의 주소를 구분할 수 있다.

다만, 서브마스크로 가려지지 않은 모든 비트값이 1이 될 경우에는 받는 UDP신호는 Broadcast UDP로 인식이 되어서 특정 IP가 아닌 모든 디바이스에 동일하게 전송하도록 약속이 되어 있다.



소스코드

Broadcast UDP의 규약과 구글 검색을 통한 MSDN 문서를 좀 더 찾아보면 간단히 찾아 볼 수 있다. 물론 스택오버플로어의 내용에다가 약간의 문자열을 파지 하는 방법도 있지만, 가능하면 MS에서 만들어 놓은 class를 활용을 해보도록 하자.


using System;
using System.Net.NetworkInformation;

class Program
{

// print ip address, submask
private void PrintIpAddressAndSubmask()
{
    NetworkInterface[] netInter = NetworkInterface.GetAllNetworkInterfaces();
    foreach(var itInter in netInter)
    {
        // 인터넷 혹은 망에 연결된 것만 남김
        if(itInter.OperationsalStatus != OperationalStatus.Up)
            continue;
        // 유선(Ethernet)과 무선(Wireless80211)만 남김
        if(itInter.NetworkInterfaceType != NetworkInterfaceType.Ethernet
            && itInter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211)
            continue;
        var unicast = itInter.GetIpProperties().UnicastAddress;
        foreach(var itUnicast in unicast)
        {
            // Ip v6는 걸러냄
            if(itUnicast.IPv4Mask.Address == 0)
                continue;
            console.writeLine("\tIp Address : {0}", itUnicast.Address);
            console.writeLine("\tSubnet Mask : {0}", itUnicast.IPv4mask);
        }
    }
}

}





참조 자료

MSDN : UDP 서비스 사용 문서
MSDN : NetworkInterface 클래스



반응형

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

C# Winform : Detect Resize (크기 변화 감지)  (0) 2018.02.07
C# : Queue VS ConcurrentQueue  (0) 2018.02.02
C# : const vs readonly  (0) 2017.10.25
C# 싱글톤 패턴 상속용 클래스  (0) 2017.09.29
C# 싱글톤 패턴 : 동적 싱글톤  (0) 2017.09.04
반응형

C# : const vs readonly

정리하게 된 배경

C++를 주로 사용하던 사람에게는 const라는 키워드가 상당히 익숙하다. 나중에 만들어진 C#은 당연히 기존의 관용적으로 사용되는 것을 보완 하는 방식으로 발전을 하게 되었다. 문제는 C#의 기본서를 통해서 readonly와 const의 차이점에 대한 설명이 부족하다. 때문에 이를 보완하기 위하여 기록한다.


const

 constant(일정한)의 앞글자 머리에서 따왔듯이 변하지 않는다는 의미로 사용된다. 하지만, 초기 컴퓨터는 대부분 수치를 다루다보니 키워드에 대해서 "상수"로 번역이 되어진것 같다. 하지만, 실제 의미는 원 단어에서 온것 처럼 변하지 않는 것을 의미한다(물론 그 것도 메모리상에서는 숫자에 불과하긴하다). 이때문에 선언시 초기화 된 값을 읽을 수 밖에 없다.


readonly

 C#에서 등장한 키워드로 직역만 해도 앞의 const와 동일하게 의미를 갖고 있다. 그럼 같은걸 뭐하로 const와 readonly 둘 다 사용하게 만들었을까? 가독성을 높이기 위해서 일까?

 사실 const와 readonly는 차이가 있다. const의 경우 말그대로 선언시 반드시 초기화를 해줘야 한다. 하지만, readonly는 선언시 반드시 초기화 할 필요가 없다. 하지만, readonly이지 않는가? 그렇다. 클래스의 맴버변수(혹은 필드)로 있을때 한번만 정의가 될 수 있다. 즉, 생성자로 클래스의 인스턴스가 생성될때 결정되고 변하지 않는 값을 사용할때 적절하다.

 물론 프로퍼티를 사용해도 같은 기능을 충분히 구현할 수 있다. 다만, 이러한 기능을 사용할지 않할지는 사용하는 개발자의 몫이다.




반응형
반응형

C# 싱글톤 패턴 상속용 클래스

알게된 배경

싱글톤 패턴은 비교적 자주 사용되는 패턴이고, 멀티 스레드 프로그램을 작성할 경우 서로 상성이 안좋기 때문에 크리티컬 섹션에 대해서 뮤텍스(C#에서는 락)을 잡아줘야 한다. 문제는 이러한 패턴은 매번 반복이 되기 때문에 부모 클래스로서 작성을 해놓고, 상속받아서 기능을 구현하는 것이 여러모로 편리하다고 생각이 들기 때문에 찾아보니 해당 패턴이 있었다.

 그래서 메모겸 샘플 코드를 남긴다


부모 싱글톤

예시)

namespace Single
{
    public class Singleton<T> where T : class, new()
    {
        protected static object _instanceLock = new object();
        protected static volatile T _instance;
        public static T Instance
        {
            get
            {
                lock(_instanceLock)
                {
                    if(null == _instance)
                         _instance = new T();
                }
                return _instance;
            }
        }

    }
}



자식 싱글톤

예시)

namespace Single
{
    class MyClass : Singleton<MyClass>
    {
        // contents
        public void Print()
        {
            Console.WriteLine("Hello Singleton?");
        }
    }
}



참고자료



반응형

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

C# : 닷넷에서 Broadcast UDP를 보내기(1)  (0) 2018.01.29
C# : const vs readonly  (0) 2017.10.25
C# 싱글톤 패턴 : 동적 싱글톤  (0) 2017.09.04
C# Socket : 고정 구조체 만들기  (0) 2017.07.11
C# WinForm : 문자 픽셀 측정  (0) 2017.07.06
반응형

C# 싱글톤 패턴 : 동적 싱글톤

사용 목적

 하나만 있어야 하는 경우 사용되는 패턴이다. 보통 매니져 형태로 사용되는 경우에 사용된다. 간단하면서 의외로 자주 사용되는 패턴이기도 하다.

 하지만 최근에 멀티 쓰래딩을 구현할 경우 주의가 필요한 패턴이다. 보통 뮤텍스(크리티컬 섹션)와 같은 lock 기능을 통해서 동시에 접근하는 것을 막기도 하는데, 이런일이 잦을 수록 성능 향상을 저해 한다는 점도 있다.


싱글톤을 위한 기본 상식

 싱글톤(Singleton)이라는 단어에서 알 수 있듯이 하나만 존재하는 객체를 의미한다. 그래서 클래스라는 틀로 찍힌 인스턴스(실제 객체)는 하나만 존재 해야 한다.


 이 처럼 하나만 갖게 되어야 하는 조건을 충족 시키기 위해서 다음 조건들을 충족 해야 한다

 1. 생성자를 외부에서 접근되어서는 안된다(하나만 있어야 하기 때문)

 2. 인스턴스(실제 객체)는 하나만 있어야 한다.


 특히 1번의 조건을 충족하기 위해서 대부분 클래스를 지원하는 언어에서는 생성자를 private 혹은 protected 로 정의하여 외부에서 접근을 못하도록 막는 패턴을 갖게 된다.

 2번의 조건을 충족시키기 위해서는 내부에 인스턴스를 갖고 있되 접근이 가능하도록 해야 한다. 따라서 이를 위해 static으로 선언을 하게 된다. 이렇게 선언된 변수 혹은 객체는 해당 클래스를 통해서만 접근이 가능하게 된다. 그와 동시에 글로벌 영역에 객체가 존재하게 된다.

 그리고 마지막으로 싱글톤을 사용할 수 있도록 Instance 메서드를 만든다. C++의 경우 GetInstance()로 선언하는것이 관용적이지만, C#에서는 프로퍼티 기능이 있기 때문에 Get 관용구를 생략하는 경우가 있다.


구현 방법

동적 싱글톤 패턴 이기 때문에 인스턴스를 요청할때 없는 경우 생성해서 인스턴스를 반환하게 구현하면 된다.

예시)

using System;
public class Singleton
{
    private static Singleton _instance;
    private Singleton() {}
    public static Singleton Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}




위는 정석적인 방법이고 편의상 ? 연산을 이용하여 한줄로 선언할 수 있다. 이러한 방법은 코드를 간결하게 하지만, lock을 이용한 크리티컬 섹션을 설정할 수 없는 단점이 있다.

예시)

using System;
public class Singleton
{
    private static Singleton _instance;
    private Singleton() {}
    public static Singleton Instance
    {
        get
        {
            return (null == _instance) ? _instance = new Singleton() : _instance;
        }
    }
}




참조 자료

MSDN 싱글톤 패턴 문서



반응형

+ Recent posts