Share via


연습: C# 또는 Visual Basic으로 간단한 구성 요소를 만들고 JavaScript에서 해당 구성 요소 호출

이 연습에서는 Visual Basic 또는 C#과 함께 .NET Framework를 사용하여 Windows 런타임 구성 요소에 패키징된 Windows 런타임 형식을 만드는 방법과 JavaScript를 사용하여 Windows용으로 빌드된 Windows 스토어 앱에서 구성 요소를 호출하는 방법을 보여 줍니다.

Visual Studio에서는 쉽게 C# 또는 Visual Basic으로 작성된 Windows 런타임 구성 요소를 앱에 추가하고 JavaScript에서 호출할 수 있는 Windows 런타임 형식을 만들 수 있습니다. 내부적으로 Windows 런타임 형식은 Windows 스토어 앱에서 허용되는 모든 .NET Framework 기능을 사용할 수 있습니다. 자세한 내용은 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기Windows 스토어 앱용 .NET 개요를 참조하십시오. 외부적으로 형식의 멤버는 매개 변수 및 반환 값에 대해 Windows 런타임 형식만 노출할 수 있습니다. 솔루션을 빌드할 때 Visual Studio에서는 .NET Framework Windows 런타임 구성 요소 프로젝트를 빌드한 다음 Windows 메타데이터(.winmd) 파일을 만드는 빌드 단계를 실행합니다. 이것이 Visual Studio에서 앱에 포함하는 Windows 런타임 구성 요소입니다.

참고

.NET Framework에서는 기본 데이터 형식 및 컬렉션 형식과 같은 일반적으로 사용되는 .NET Framework 형식을 해당하는 Windows 런타임 형식에 자동으로 매핑합니다.이러한 .NET Framework 형식은 Windows 런타임 구성 요소의 공용 인터페이스에서 사용될 수 있으며 구성 요소의 사용자에게 해당하는 Windows 런타임 형식으로 나타납니다.C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기을 참조하십시오.

이 연습에서는 다음 작업을 수행합니다. JavaScript를 사용하여 Windows 스토어 앱을 설정하는 첫 번째 단원을 완료한 후 나머지 단원들을 순서에 관계없이 완료할 수 있습니다.

  • 간단한 Windows 런타임 클래스 만들기

  • JavaScript 및 관리 코드에서 Windows 런타임 사용

  • 구성 요소에서 관리되는 형식 반환

  • 이벤트 선언

  • 비동기 작업 노출

필수 구성 요소:

이 연습을 진행하려면 먼저 다음 작업을 수행해야 합니다.

  • Windows 8 이상

  • Microsoft Visual Studio 2012 또는 Microsoft Visual Studio Express 2012 for Windows 8 이상

간단한 Windows 런타임 클래스 만들기

이 단원에서는 JavaScript를 사용하여 Windows용으로 작성된 Windows 스토어 앱을 만들고 Visual Basic 또는 C# Windows 런타임 구성 요소 프로젝트를 추가합니다. 또한 관리되는 Windows 런타임 형식을 정의하고 JavaScript에서 형식의 인스턴스를 만든 다음 정적 멤버와 인스턴스 멤버를 호출하는 방법을 보여 줍니다. 구성 요소에 중점을 두기 위해 예제 앱은 의도적으로 단순하게 표시되었습니다. 예제 앱을 원하는 대로 만들어 보십시오.

  1. Visual Studio에서 새로운 JavaScript 프로젝트를 만듭니다. 메뉴 모음에서 파일, 새로 만들기, 프로젝트를 차례로 선택합니다(Visual Studio Express에서는 파일, 새 프로젝트를 차례로 선택함). 새 프로젝트 대화 상자의 설치된 템플릿 섹션에서 JavaScript를 선택한 다음 Windows 스토어를 선택합니다. Windows 스토어를 사용할 수 없는 경우 Windows 8 이상을 사용하고 있는지 확인하십시오. 새 응용 프로그램 템플릿을 선택하고 프로젝트 이름으로 SampleApp를 입력합니다.

  2. 구성 요소 프로젝트를 만듭니다. 솔루션 탐색기에서 SampleApp 솔루션의 바로 가기 메뉴를 열고 추가를 선택한 다음 새 프로젝트를 선택하여 새로운 C# 또는 Visual Basic 프로젝트를 솔루션에 추가합니다. 새 프로젝트 추가 대화 상자의 설치된 템플릿 섹션에서 Visual Basic 또는 **Visual C#**을 선택한 다음 Windows 스토어를 선택합니다. Windows 런타임 구성 요소 템플릿을 선택하고 프로젝트 이름으로 SampleComponent를 입력합니다.

  3. 클래스의 이름을 Example로 변경합니다. 기본적으로 클래스는 public sealed(Visual Basic에서는 Public NotInheritable)로 표시됩니다. 구성 요소에서 노출하는 모든 Windows 런타임 클래스는 봉인되어야 합니다.

  4. 클래스에 static 메서드(Visual Basic에서는 Shared 메서드)와 인스턴스 속성을 간단한 두 멤버로 추가합니다.

    namespace SampleComponent
    {
        public sealed class Example
        {
            public static string GetAnswer() 
            { 
                return "The answer is 42."; 
            }
    
            public int SampleProperty { get; set; }
        }
    }
    
    Public NotInheritable Class Example
        Public Shared Function GetAnswer() As String
            Return "The answer is 42."
        End Function
    
        Public Property SampleProperty As Integer
    End Class
    
  5. 선택 사항: 새로 추가된 멤버에 대해 IntelliSense를 사용하도록 설정하려면 솔루션 탐색기에서 SampleComponent 프로젝트의 바로 가기 메뉴를 열고 빌드를 선택합니다.

  6. 솔루션 탐색기의 JavaScript 프로젝트에서 참조의 바로 가기 메뉴를 연 다음 참조 추가를 선택하여 참조 관리자를 엽니다. 솔루션을 선택한 다음 프로젝트를 선택합니다. SampleComponent 프로젝트의 확인란을 선택하고 확인을 선택하여 참조를 추가합니다.

Hh779077.collapse_all(ko-kr,VS.120).gifJavaScript에서 구성 요소 호출

JavaScript에서 Windows 런타임 형식을 사용하려면 다음 코드를 default.js 파일(프로젝트의 js 폴더에 있음)의 끝에서 Visual Studio 템플릿이 제공하는 익명 함수 뒤에 추가합니다.

var ex;

function basics1() {
    document.getElementById('output').innerHTML =
        SampleComponent.Example.getAnswer();

    ex = new SampleComponent.Example();

    document.getElementById('output').innerHTML += "<br/>" + 
        ex.sampleProperty;
}

function basics2() {
    ex.sampleProperty += 1;
    document.getElementById('output').innerHTML += "<br/>" + 
        ex.sampleProperty;
}

각 멤버 이름의 첫 문자가 대문자에서 소문자로 변경되었습니다. 이 변환은 Windows 런타임을 자연스럽게 사용할 수 있도록 하기 위해 JavaScript에서 제공하는 지원의 일부입니다. 네임스페이스 및 클래스 이름은 파스칼식 대/소문자를 사용합니다. 멤버 이름은 모두 소문자인 이벤트 이름을 제외하고 카멜식 대/소문자를 사용합니다. JavaScript에서 Windows 런타임 사용을 참조하세요. 카멜식 대/소문자 규칙은 혼동될 수 있습니다. 일련의 첫 대문자들은 일반적으로 소문자로 나타나지만 대문자 3개 뒤에 소문자 하나가 오는 경우 처음 두 문자만 소문자로 나타납니다. 예를 들어 IDStringKind라는 멤버는 idStringKind로 나타납니다. Visual Studio에서 Windows 런타임 구성 요소 프로젝트를 빌드한 다음 JavaScript 프로젝트에서 IntelliSense를 사용하여 올바른 대/소문자를 볼 수 있습니다.

마찬가지로 .NET Framework는 관리 코드에서 Windows 런타임을 자연스럽게 사용할 수 있도록 지원합니다. 이에 대해서는 이 문서의 이후 단원과 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기.NET Framework Support for Windows Store Apps and Windows Runtime 문서에서 설명합니다.

Hh779077.collapse_all(ko-kr,VS.120).gif간단한 사용자 인터페이스 만들기

JavaScript 프로젝트에서 default.html 파일을 열고 다음 코드와 같이 body를 업데이트합니다. 이 코드는 예제 앱의 전체 컨트롤 집합을 포함하며 클릭 이벤트에 대한 함수 이름을 지정합니다.

경고

처음 앱을 실행할 때에는 Basics1Basics2 단추만 지원됩니다.

<body>
    <div id="buttons">
        <button onclick="basics1();">Basics 1</button>
        <button onclick="basics2();">Basics 2</button>

        <button onclick="runtime1();">Runtime 1</button>
        <button onclick="runtime2();">Runtime 2</button>

        <button onclick="returns1();">Returns 1</button>
        <button onclick="returns2();">Returns 2</button>

        <button onclick="events1();">Events 1</button>

        <button id="btnAsync" onclick="asyncRun();">Async</button>
        <button id="btnCancel" onclick="asyncCancel();" disabled="disabled">Cancel Async</button>
        <progress id="primeProg" value="25" max="100" style="color: yellow;"></progress>
    </div>
    <div id="output">
        
    </div>
</body>

JavaScript 프로젝트의 css 폴더에서 default.css를 엽니다. 표시된 대로 body를 수정하고 단추 레이아웃과 출력 텍스트의 배치를 제어하기 위해 스타일을 추가합니다.

body
{
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr 14fr;
    display: -ms-grid;
}

#buttons {
    -ms-grid-rows: 1fr;
    -ms-grid-columns: auto;
    -ms-grid-row-align: start;
}
#output {
    -ms-grid-row: 2;
    -ms-grid-column: 1;
}

Hh779077.collapse_all(ko-kr,VS.120).gif앱 빌드 및 실행

솔루션을 빌드하고 실행하려면 F5 키를 선택합니다. SampleComponent가 정의되지 않았다는 런타임 오류 메시지가 나타나는 경우 클래스 라이브러리 프로젝트에 대한 참조가 없는 것입니다.

Visual Studio에서는 먼저 클래스 라이브러리를 컴파일한 다음 Winmdexp.exe(Windows Runtime 메타데이터 내보내기 도구)를 실행하는 MSBuild 작업을 실행하여 Windows 런타임 구성 요소를 만듭니다. 구성 요소는 관리 코드뿐만 아니라 코드를 설명하는 Windows 메타데이터를 포함하는 .winmd 파일에 포함되어 있습니다. WinMdExp.exe는 Windows 런타임 구성 요소에서 유효하지 않은 코드를 작성하는 경우 빌드 오류 메시지를 생성하고 오류 메시지는 Visual Studio IDE에서 표시됩니다. Visual Studio에서는 구성 요소를 Windows 스토어 앱의 앱 패키지(.appx 파일)에 추가하고 적절한 매니페스트를 생성합니다.

Basics 1 단추를 선택하여 정적 GetAnswer 메서드의 반환 값을 출력 영역에 할당하고 Example 클래스의 인스턴스를 만든 다음 SampleProperty 속성의 값을 출력 영역에 표시합니다. 아래와 같은 출력이 표시됩니다.

"The answer is 42."
0

Basics 2 단추를 선택하여 SampleProperty 속성의 값을 늘리고 출력 영역에 새 값을 표시합니다. 문자열 및 숫자와 같은 기본 형식을 매개 변수 형식 및 반환 형식으로 사용할 수 있으며 관리 코드와 JavaScript 간에 전달할 수 있습니다. JavaScript의 숫자는 배정밀도 부동 소수점 형식으로 저장되기 때문에 .NET Framework 숫자 형식으로 변환됩니다.

참고

기본적으로 JavaScript 코드에만 중단점을 설정할 수 있습니다.Visual Basic 또는 C# 코드를 디버깅하려면 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기를 참조하십시오.

디버깅을 중지하고 앱을 닫으려면 앱에서 Visual Studio로 전환하고 Shift+F5를 선택합니다.

JavaScript 및 관리 코드에서 Windows 런타임 사용

JavaScript 또는 관리 코드에서 Windows 런타임을 호출할 수 있습니다. Windows 런타임 개체가 JavaScript와 관리 코드 사이를 오가며 전달될 수 있고, 둘 중 한 쪽에서 이벤트를 처리할 수 있습니다. 그러나 JavaScript 및 .NET Framework에서 Windows 런타임을 서로 다르게 지원하기 때문에 두 환경에서 Windows 런타임 형식을 사용하는 방법은 몇 가지 측면에서 서로 다릅니다. 다음 예제에서는 Windows.Foundation.Collections.PropertySet 클래스를 사용하여 이러한 차이를 설명합니다. 이 예제에서는 관리 코드에서 PropertySet 컬렉션의 인스턴스를 만들고 컬렉션의 변경 내용을 추적하기 위해 이벤트 처리기를 등록합니다. 그런 다음 컬렉션을 가져오는 JavaScript 코드를 추가하고 고유한 이벤트 처리기를 등록한 후 컬렉션을 사용합니다. 마지막으로 관리 코드에서 컬렉션을 변경하고 관리되는 예외를 처리하는 JavaScript를 보여 주는 메서드를 추가합니다.

중요

이 예제에서는 UI 스레드에서 이벤트가 발생합니다.예를 들어 비동기 호출의 백그라운드 스레드에서 이벤트를 발생시키는 경우 JavaScript가 이벤트를 처리할 수 있도록 몇 가지 추가 작업을 수행해야 합니다.자세한 내용은 Windows 런타임 구성 요소에서 이벤트 발생시키기을 참조하십시오.

SampleComponent 프로젝트에서 PropertySetStats라는 새로운 public sealed 클래스(Visual Basic에서는 Public NotInheritable)를 추가합니다. 클래스는 PropertySet 컬렉션을 래핑하고 해당 MapChanged 이벤트를 처리합니다. 이벤트 처리기는 발생하는 각 형식의 변경 사항 수를 추적하고 DisplayStats 메서드는 HTML로 서식이 지정된 보고서를 생성합니다. 추가 using 문(Visual Basic에서는 Imports 문)에 유의하세요. 기존 using 문을 덮어쓰지 않고 이 문을 추가해야 합니다.

using Windows.Foundation.Collections;

namespace SampleComponent
{
    public sealed class PropertySetStats
    {
        private PropertySet _ps;
        public PropertySetStats()
        {
            _ps = new PropertySet();
            _ps.MapChanged += this.MapChangedHandler;
        }

        public PropertySet PropertySet { get { return _ps; } }

        int[] counts = { 0, 0, 0, 0 };
        private void MapChangedHandler(IObservableMap<string, object> sender,
            IMapChangedEventArgs<string> args)
        {
            counts[(int)args.CollectionChange] += 1;
        }

        public string DisplayStats()
        {
            StringBuilder report = new StringBuilder("<br/>Number of changes:<ul>");
            for (int i = 0; i < counts.Length; i++)
            {
                report.Append("<li>" + (CollectionChange)i + ": " + counts[i] + "</li>");
            }
            return report.ToString() + "</ul>";
        }
    }
}
Imports System.Text

Public NotInheritable Class PropertySetStats
    Private _ps As PropertySet
    Public Sub New()
        _ps = New PropertySet()
        AddHandler _ps.MapChanged, AddressOf Me.MapChangedHandler
    End Sub

    Public ReadOnly Property PropertySet As PropertySet
        Get
            Return _ps
        End Get
    End Property

    Dim counts() As Integer = {0, 0, 0, 0}
    Private Sub MapChangedHandler(ByVal sender As IObservableMap(Of String, Object),
        ByVal args As IMapChangedEventArgs(Of String))

        counts(CInt(args.CollectionChange)) += 1
    End Sub

    Public Function DisplayStats() As String
        Dim report As New StringBuilder("<br/>Number of changes:<ul>")
        For i As Integer = 0 To counts.Length - 1
            report.Append("<li>" & CType(i, CollectionChange).ToString() &
                          ": " & counts(i) & "</li>")
        Next
        Return report.ToString() & "</ul>"
    End Function
End Class

이벤트 전송자(이 경우 PropertySet 개체)가 Windows 런타임 인터페이스 IObservableMap<K, V>의 인스턴스인 IObservableMap<string, object> 인터페이스(Visual Basic에서는 IObservableMap(Of String, Object))로 캐스팅되는 경우를 제외하고, 이벤트 처리기는 익숙한 .NET Framework 이벤트 패턴을 따릅니다. 필요한 경우 전송자를 해당 형식으로 캐스팅할 수 있습니다. 또한 이벤트 인수는 개체로 표현되지 않고 인터페이스로 표현됩니다.

default.js 파일에서 Runtime1 함수를 표시된 대로 추가합니다. 이 코드에서는 PropertySetStats 개체를 만들고 PropertySet 컬렉션을 가져온 다음 MapChanged 이벤트를 처리하기 위해 고유한 이벤트 처리기인 onMapChanged 함수를 추가합니다. 컬렉션을 변경한 후 runtime1은 DisplayStats 메서드를 호출하여 변경 형식에 대한 요약을 표시합니다.

var propertysetstats;

function runtime1() {
    document.getElementById('output').innerHTML = "";

    propertysetstats = new SampleComponent.PropertySetStats();
    var propertyset = propertysetstats.propertySet;

    propertyset.addEventListener("mapchanged", onMapChanged);

    propertyset.insert("FirstProperty", "First property value");
    propertyset.insert("SuperfluousProperty", "Unnecessary property value");
    propertyset.insert("AnotherProperty", "A property value");

    propertyset.insert("SuperfluousProperty", "Altered property value")
    propertyset.remove("SuperfluousProperty");

    document.getElementById('output').innerHTML +=
        propertysetstats.displayStats();
}

function onMapChanged(change) {
    var result
    switch (change.collectionChange) {
        case Windows.Foundation.Collections.CollectionChange.reset:
            result = "All properties cleared";
            break;
        case Windows.Foundation.Collections.CollectionChange.itemInserted:
            result = "Inserted " + change.key + ": '" + 
                change.target.lookup(change.key) + "'";
            break;
        case Windows.Foundation.Collections.CollectionChange.itemRemoved:
            result = "Removed " + change.key;
            break;
        case Windows.Foundation.Collections.CollectionChange.itemChanged:
            result = "Changed " + change.key + " to '" + 
                change.target.lookup(change.key) + "'";
            break;
    }

    document.getElementById('output').innerHTML +=
        "<br/>" + result;
}

JavaScript에서 Windows 런타임 이벤트를 처리하는 방식은 .NET Framework 코드에서 처리하는 방식과 크게 다릅니다. JavaScript 이벤트 처리기는 인수를 하나만 사용합니다. Visual Studio 디버거에서 이 개체를 볼 때 첫 번째 속성은 전송자입니다. 이벤트 인수 인터페이스의 멤버도 이 개체에 직접 나타납니다.

앱을 실행하려면 F5 키를 선택합니다. 클래스가 봉인되지 않은 경우 "'SampleComponent.Example' unsealed 형식은 현재 내보낼 수 없습니다. 이 형식을 sealed로 표시하세요."라는 오류 메시지가 표시됩니다.

Runtime 1 단추를 선택합니다. 이벤트 처리기는 요소가 추가되거나 변경될 때 변경 사항을 표시하고 DisplayStats 메서드가 끝에 호출되어 개수에 대한 요약을 생성합니다. 디버깅을 중지하고 앱을 닫으려면 Visual Studio로 다시 전환하고 Shift+F5를 선택합니다.

관리 코드에서 PropertySet 컬렉션에 두 항목을 더 추가하려면 다음 코드를 PropertySetStats 클래스에 추가합니다.

        public void AddMore()
        {
            _ps.Add("NewProperty", "New property value");
            _ps.Add("AnotherProperty", "A property value");
        }
    Public Sub AddMore()
        _ps.Add("NewProperty", "New property value")
        _ps.Add("AnotherProperty", "A property value")
    End Sub

이 코드에서는 두 환경에서 Windows 런타임 형식을 사용하는 방식의 또 다른 차이점을 보여 줍니다. 이 코드를 직접 입력하는 경우 JavaScript 코드에서 사용한 insert 메서드를 IntelliSense에서 표시하지 않음을 알 수 있습니다. 대신 IntelliSense에서는 .NET Framework에서 컬렉션에 대해 흔히 표시되는 Add 메서드를 표시합니다. 이는 Windows 런타임 및 .NET Framework에서 일반적으로 사용되는 일부 컬렉션 인터페이스가 이름은 다르지만 유사한 기능을 수행하기 때문입니다. 이러한 인터페이스가 관리 코드에서 사용되는 경우에는 해당하는 .NET Framework 항목으로 나타납니다. 이에 대해서는 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기에서 설명합니다. JavaScript에서 동일한 인터페이스를 사용하는 경우 Windows 런타임에서 변경된 사항은 멤버 이름 시작 부분의 대문자들이 소문자가 된 것뿐입니다.

마지막으로 예외 처리를 사용하여 AddMore 메서드를 호출하려면 runtime2 함수를 default.js에 추가합니다.

function runtime2() {
    try {
        propertysetstats.addMore();
    }
    catch (ex) {
        document.getElementById('output').innerHTML +=
            "<br/><b>" + ex + "</b>";
    }

    document.getElementById('output').innerHTML +=
        propertysetstats.displayStats();
}

앱을 실행하려면 F5 키를 선택합니다. Runtime 1을 선택한 다음 Runtime 2를 선택합니다. JavaScript 이벤트 처리기는 컬렉션에 대한 첫 번째 변경 사항을 보고합니다. 그러나 두 번째 변경 사항에는 중복 키가 있습니다. .NET Framework 사전의 사용자는 Add 메서드가 예외를 throw할 것으로 기대하며 실제로 예외가 throw됩니다. JavaScript에서 .NET Framework 예외를 처리합니다.

참고

JavaScript 코드에서는 예외의 메시지를 표시할 수 없습니다.메시지 텍스트는 스택 추적으로 대체됩니다.자세한 내용은 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기의 "예외 throw"를 참조하십시오.

이와 대조적으로 JavaScript에서 중복 키를 사용하여 insert 메서드를 호출한 경우에는 항목의 값이 변경되었습니다. 이러한 동작의 차이는 C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기에 설명되어 있듯이 JavaScript와 .NET Framework에서 Windows 런타임을 지원하는 방식이 서로 다르기 때문입니다.

구성 요소에서 관리되는 형식 반환

이전에 설명한 대로 기본 Windows 런타임 형식을 JavaScript 코드와 C# 또는 Visual Basic 코드 간에 자유롭게 전달할 수 있습니다. 대부분 형식 이름과 멤버 이름은 두 경우에 동일합니다(멤버 이름이 JavaScript에서 소문자로 시작하는 경우 제외). 그러나 이전 섹션에서 PropertySet 클래스는 관리 코드의 멤버가 다른 것으로 나타났습니다. 예를 들어 JavaScript에서 insert 메서드를 호출했고 .NET Framework 코드에서는 Add 메서드를 호출했습니다. 이 단원에서는 이러한 차이점이 JavaScript에 전달되는 .NET Framework 형식에 어떠한 영향을 주는지를 살펴봅니다.

구성 요소에서 만들었거나 JavaScript에서 구성 요소에 전달한 Windows 런타임 형식을 반환하는 것 외에 관리 코드에서 만들어진 관리되는 형식을 해당하는 Windows 런타임 형식이 있는 것처럼 JavaScript에 반환할 수 있습니다. 런타임 클래스의 첫 번째 간단한 예제에서도 멤버의 매개 변수와 반환 형식은 .NET Framework 형식인 Visual Basic 또는 C# 기본 형식입니다. 컬렉션에 대해 이를 보여 주려면 다음 코드를 Example 클래스에 추가하여 정수로 인덱싱된 문자열의 제네릭 사전을 반환하는 메서드를 만듭니다.

        public static IDictionary<int, string> GetMapOfNames()
        {
            Dictionary<int, string> retval = new Dictionary<int, string>();
            retval.Add(1, "one");
            retval.Add(2, "two");
            retval.Add(3, "three");
            retval.Add(42, "forty-two");
            retval.Add(100, "one hundred");
            return retval;
        }
    Public Shared Function GetMapOfNames() As IDictionary(Of Integer, String)
        Dim retval As New Dictionary(Of Integer, String)
        retval.Add(1, "one")
        retval.Add(2, "two")
        retval.Add(3, "three")
        retval.Add(42, "forty-two")
        retval.Add(100, "one hundred")
        Return retval
    End Function

사전은 Dictionary<TKey, TValue>에 의해 구현되고 Windows 런타임 인터페이스에 매핑되는 인터페이스로 반환되어야 합니다. 이 경우 인터페이스는 IDictionary<int, string>(Visual Basic에서는 IDictionary(Of Integer, String))입니다. Windows 런타임 형식 IMap<int, string>은 관리 코드에 전달될 때 IDictionary<int, string>로 나타나며, 이는 관리되는 형식이 JavaScript에 전달되는 경우에도 마찬가지입니다.

중요

관리되는 형식이 여러 인터페이스를 구현하는 경우 JavaScript에서는 목록에서 가장 먼저 나타나는 인터페이스를 사용합니다.+예를 들어 JavaScript 코드에 Dictionary<int, string>을 반환하는 경우 사용자가 반환 형식으로 지정하는 인터페이스에 관계없이 반환 형식은 IDictionary<int, string>으로 나타납니다.즉 첫 번째 인터페이스가 나머지 인터페이스에 나타나는 멤버를 포함하고 있지 않은 경우 해당 멤버는 JavaScript에 표시되지 않습니다.

새 메서드를 테스트하고 사전을 사용하려면 returns1 및 returns2 함수를 default.js에 추가합니다.

var names;

function returns1() {
    names = SampleComponent.Example.getMapOfNames();
    document.getElementById('output').innerHTML = showMap(names);
}
 
ct = 7

function returns2() {
    if (!names.hasKey(17)) {
        names.insert(43, "forty-three");
        names.insert(17, "seventeen");
    }
    else {
        var err = names.insert("7", ct++);
        names.insert("forty", "forty");
    }
    document.getElementById('output').innerHTML = showMap(names);
}
 
function showMap(map) {
    var item = map.first();
    var retval = "<ul>";
    
    for (var i = 0, len = map.size; i < len; i++) {
        retval += "<li>" + item.current.key + ": " + item.current.value + "</li>";
        item.moveNext();
    }
    return retval + "</ul>";
}

이 JavaScript 코드에서 관찰할 몇 가지 흥미로운 사실이 있습니다. 우선, 이 코드에는 사전의 내용을 HTML로 표시하기 위해 showMap 함수가 포함되어 있습니다. showMap에 대한 코드에서 반복 패턴에 유의하세요. .NET Framework에서는 제네릭 IDictionary 인터페이스에 대한 First 메서드가 없으며, 크기가 Size 메서드에 의해 반환되지 않고 Count 속성에 의해 반환됩니다. JavaScript에서 IDictionary<int, string>는 Windows 런타임 형식 IMap<int, string>인 것처럼 나타납니다. IMap<K,V> 인터페이스를 참조하세요.

이전 예제와 같이 returns2 함수에서 JavaScript는 Insert 메서드(JavaScript에서는 insert)를 호출하여 사전에 항목을 추가합니다.

앱을 실행하려면 F5 키를 선택합니다. 사전의 초기 내용을 만들고 표시하려면 Returns 1 단추를 선택하고, 사전에 두 항목을 더 추가하려면 Returns 2 단추를 선택합니다. Dictionary<TKey, TValue>에서 예상하듯이 항목은 삽입 순서대로 표시됩니다. 항목을 정렬하려는 경우 GetMapOfNames에서 SortedDictionary<int, string>를 반환할 수 있습니다. 이전 예제에서 사용되는 PropertySet 클래스의 내부 구성은 Dictionary<TKey, TValue>와 다릅니다.

물론 JavaScript는 강력한 형식의 언어가 아니므로 강력한 형식의 제네릭 컬렉션을 사용하면 놀라운 결과가 발생할 수 있습니다. Returns 2 단추를 다시 선택합니다. JavaScript에서는 "7"을 숫자 7로, ct에 저장된 숫자 7을 문자열로 강제 변환합니다. 또한 문자열 "forty"를 0으로 강제 변환합니다. 그러나 이는 시작에 불과합니다. Returns 2 단추를 몇 번 더 선택합니다. 관리 코드에서 Add 메서드는 값이 올바른 형식으로 캐스팅된 경우에도 중복 키 예외를 생성합니다. 이와 대조적으로 Insert 메서드는 기존 키와 연결된 값을 업데이트하고 새 키가 사전에 추가되었는지 여부를 나타내는 Boolean 값을 반환합니다. 이 때문에 키 7과 연결된 값이 계속 변경됩니다.

또 다른 예기치 않은 동작은 할당되지 않은 JavaScript 변수를 문자열 인수로 전달하는 경우 "undefined"라는 문자열을 얻게 된다는 것입니다. 즉, .NET Framework 컬렉션 형식을 JavaScript 코드에 전달할 때는 주의해야 합니다.

참고

연결할 많은 양의 텍스트가 있는 경우에는 코드를 .NET Framework 메서드로 이동하고 showMap 함수에서와 같이 StringBuilder 클래스를 사용하여 보다 효율적으로 이 작업을 수행할 수 있습니다.

Windows 런타임 구성 요소에서 사용자 고유의 제네릭 형식을 노출할 수 없지만 다음과 같은 코드를 사용하여 Windows 런타임 클래스에 대한 .NET Framework 제네릭 컬렉션을 반환할 수 있습니다.

        public static object GetListOfThis(object obj)
        {
            Type target = obj.GetType();
            return Activator.CreateInstance(typeof(List<>).MakeGenericType(target));
        }
    Public Shared Function GetListOfThis(obj As Object) As Object
        Dim target As Type = obj.GetType()
        Return Activator.CreateInstance(GetType(List(Of )).MakeGenericType(target))
    End Function

List<T>는 JavaScript에서 Windows 런타임 형식 IVector<T>로 나타나는 IList<T>를 구현합니다.

이벤트 선언

표준 .NET Framework 이벤트 패턴이나 Windows 런타임에서 사용되는 다른 패턴을 사용하여 이벤트를 선언할 수 있습니다. .NET Framework에서는 System.EventHandler<TEventArgs> 대리자와 Windows 런타임 EventHandler<T> 대리자 간의 동등성을 지원하므로 EventHandler<TEventArgs>을 사용하면 표준 .NET Framework 패턴을 구현하는 데 효과적입니다. 이에 대해 살펴보려면 다음 클래스 쌍을 SampleComponent 프로젝트에 추가합니다.

namespace SampleComponent
{
    public sealed class Eventful
    {
        public event EventHandler<TestEventArgs> Test;
        public void OnTest(string msg, long number)
        {
            EventHandler<TestEventArgs> temp = Test;
            if (temp != null)
            {
                temp(this, new TestEventArgs()
                {
                    Value1 = msg,
                    Value2 = number
                });
            }
        }
    }

    public sealed class TestEventArgs
    {
        public string Value1 { get; set; }
        public long Value2 { get; set; }
    }
}
Public NotInheritable Class Eventful
    Public Event Test As EventHandler(Of TestEventArgs)
    Public Sub OnTest(ByVal msg As String, ByVal number As Long)
        RaiseEvent Test(Me, New TestEventArgs() With {
                            .Value1 = msg,
                            .Value2 = number
                            })
    End Sub
End Class

Public NotInheritable Class TestEventArgs
    Public Property Value1 As String
    Public Property Value2 As Long
End Class

Windows 런타임에서 이벤트를 노출할 때 이벤트 인수 클래스는 System.Object에서 상속합니다. EventArgs가 Windows 런타임 형식이 아니기 때문에 .NET Framework에서와 같이 System.EventArgs에서 상속하지 않습니다.

참고

이벤트에 대한 사용자 지정 이벤트 접근자(Visual Basic에서는 Custom 키워드)를 선언하는 경우 Windows 런타임 이벤트 패턴을 사용해야 합니다.Windows 런타임 구성 요소의 사용자 지정 이벤트 및 이벤트 접근자을 참조하십시오.

Test 이벤트를 처리하려면 events1 함수를 default.js에 추가합니다. events1 함수는 Test 이벤트에 대한 이벤트 처리기 함수를 만들고 OnTest 메서드를 즉시 호출하여 이벤트를 발생시킵니다. 이벤트 처리기의 본문에 중단점을 배치하는 경우 단일 매개 변수에 전달된 개체에 소스 개체와 TestEventArgs의 두 멤버가 포함되어 있는 것을 확인할 수 있습니다.

var ev;

function events1() {
    ev = new SampleComponent.Eventful();
    ev.addEventListener("test", function (e) {
        document.getElementById('output').innerHTML = e.value1;
        document.getElementById('output').innerHTML += "<br/>" + e.value2;
    });
    ev.onTest("Number of feet in a mile:", 5280);
}

비동기 작업 노출

.NET Framework에는 Task 및 제네릭 Task<TResult> 클래스를 기반으로 하는, 비동기 처리와 병렬 처리를 위한 다양한 도구 집합이 있습니다. Windows 런타임 구성 요소에서 작업 기반 비동기 처리를 노출하려면 Windows 런타임 인터페이스 IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress>를 사용합니다. Windows 런타임에서 작업(operation)은 결과를 반환하지만 동작(action)은 결과를 반환하지 않습니다.

이 단원에서는 진행률을 보고하고 결과를 반환하는 취소 가능한 비동기 작업을 보여 줍니다. GetPrimesInRangeAsync 메서드는 AsyncInfo 클래스를 사용하여 작업을 생성하고 작업 취소 및 진행률 보고 기능을 WinJS.Promise 개체에 연결합니다. 먼저 다음 using 문(Visual Basic에서는 Imports)을 Example 클래스에 추가합니다.

using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
Imports System.Runtime.InteropServices.WindowsRuntime

이제 GetPrimesInRangeAsync 메서드를 Example 클래스에 추가합니다.

        public static IAsyncOperationWithProgress<IList<long>, double> GetPrimesInRangeAsync(long start, long count)
        {
            if (start < 2 || count < 1) throw new ArgumentException();

            return AsyncInfo.Run<IList<long>, double>((token, progress) =>

                Task.Run<IList<long>>(() =>
                {
                    List<long> primes = new List<long>();
                    double onePercent = count / 100;
                    long ctProgress = 0;
                    double nextProgress = onePercent;

                    for (long candidate = start; candidate < start + count; candidate++)
                    {
                        ctProgress += 1;
                        if (ctProgress >= nextProgress)
                        {
                            progress.Report(ctProgress / onePercent);
                            nextProgress += onePercent;
                        }
                        bool isPrime = true;
                        for (long i = 2, limit = (long)Math.Sqrt(candidate); i <= limit; i++)
                        {
                            if (candidate % i == 0)
                            {
                                isPrime = false;
                                break;
                            }
                        }
                        if (isPrime) primes.Add(candidate);

                        token.ThrowIfCancellationRequested();
                    }
                    progress.Report(100.0);
                    return primes;
                }, token)
            );
        }
    Public Shared Function GetPrimesInRangeAsync(ByVal start As Long, ByVal count As Long) As IAsyncOperationWithProgress(Of IList(Of Long), Double)

        If (start < 2 Or count < 1) Then Throw New ArgumentException()

        Return AsyncInfo.Run(Of IList(Of Long), Double)( _
            Function(token, prog)
                Return Task.Run(Of IList(Of Long))( _
                    Function()
                        Dim primes As New List(Of Long)
                        Dim onePercent As Long = count / 100
                        Dim ctProgress As Long = 0
                        Dim nextProgress As Long = onePercent

                        For candidate As Long = start To start + count - 1
                            ctProgress += 1

                            If ctProgress >= nextProgress Then
                                prog.Report(ctProgress / onePercent)
                                nextProgress += onePercent
                            End If

                            Dim isPrime As Boolean = True
                            For i As Long = 2 To CLng(Math.Sqrt(candidate))
                                If (candidate Mod i) = 0 Then
                                    isPrime = False
                                    Exit For
                                End If
                            Next

                            If isPrime Then primes.Add(candidate)

                            token.ThrowIfCancellationRequested()
                        Next
                        prog.Report(100.0)
                        Return primes
                    End Function, token)
            End Function)
    End Function

GetPrimesInRangeAsync는 매우 간단한 소수 검색기인데 이는 의도적입니다. 여기에서는 비동기 작업을 구현하는 데 초점을 맞추고 있기 때문에 간결성이 중요하며, 속도가 느린 구현 성능은 취소 기능을 설명할 때 유용합니다. GetPrimesInRangeAsync는 무차별적으로 소수를 찾습니다. 소수만 사용하지 않고 제곱근보다 작거나 같은 모든 정수로 후보 숫자를 나눕니다. 이 코드를 단계별로 실행하면 다음과 같습니다.

  • 비동기 작업을 시작하기 전에 매개 변수의 유효성을 검사하고 잘못된 입력에 대한 예외를 throw하는 등의 준비 작업을 수행합니다.

  • 이 구현의 핵심은 AsyncInfo.Run<TResult, TProgress>(Func<CancellationToken, IProgress<TProgress>, Task<TResult>>) 메서드와 이 메서드의 유일한 매개 변수인 대리자입니다. 대리자는 취소 토큰과 진행률을 보고하기 위한 인터페이스를 받아들여야 하고 이러한 매개 변수를 사용하는 시작된 작업을 반환해야 합니다. JavaScript에서 GetPrimesInRangeAsync 메서드를 호출하면 다음 단계가 발생합니다(여기에 나와 있는 순서대로 발생하지 않을 수도 있음).

    • WinJS.Promise 개체는 반환된 결과를 처리하는 함수를 제공하고 취소에 대응하며 진행률 보고서를 처리합니다.

    • AsyncInfo.Run 메서드는 취소 소스와 IProgress<T> 인터페이스를 구현하는 개체를 만듭니다. 이 메서드는 대리자에 취소 소스의 CancellationToken 토큰과 IProgress<T> 인터페이스를 전달합니다.

      참고

      Promise 개체가 취소에 대응하는 함수를 제공하지 않는 경우 AsyncInfo.Run이 취소 가능한 토큰을 계속 전달하고 취소가 발생할 수 있습니다.Promise 개체가 진행률 업데이트를 처리하는 함수를 제공하지 않는 경우에는 AsyncInfo.Run이 IProgress<T>를 구현하는 개체를 계속 제공하지만 보고서가 무시됩니다.

    • 대리자는 Task.Run<TResult>(Func<TResult>, CancellationToken) 메서드를 사용하여 토큰과 진행률 인터페이스를 사용하는 시작된 작업을 만듭니다. 시작된 작업의 대리자는 원하는 결과를 계산하는 람다 함수가 제공합니다. 이에 대해 곧 자세히 살펴보겠습니다.

    • AsyncInfo.Run 메서드는 IAsyncOperationWithProgress<TResult, TProgress> 인터페이스를 구현하는 개체를 만들고, Windows 런타임 취소 메커니즘을 토큰 소스와 연결하며, Promise 개체의 진행률 보고 함수를 IProgress<T> 인터페이스와 연결합니다.

    • IAsyncOperationWithProgress<TResult, TProgress> 인터페이스가 JavaScript에 반환됩니다.

  • 시작된 작업이 나타내는 람다 함수는 인수를 사용하지 않습니다. 람다 함수이기 때문에 토큰과 IProgress 인터페이스에 액세스할 수 있습니다. 후보 숫자가 계산될 때마다 람다 함수는 다음을 수행합니다.

    • 진행률의 다음 백분율 지점에 도달했는지 확인합니다. 도달했으면 람다 함수는 IProgress<T>.Report 메서드를 호출하며, Promise 개체가 진행률을 보고하기 위해 지정한 함수를 통해 백분율이 전달됩니다.

    • 작업이 취소된 경우 취소 토큰을 사용하여 예외를 throw합니다. IAsyncInfo.Cancel 메서드(IAsyncOperationWithProgress<TResult, TProgress> 인터페이스가 상속함)가 호출된 경우 AsyncInfo.Run 메서드가 설정하는 연결을 통해 취소 토큰이 알려집니다.

  • 람다 함수가 소수의 목록을 반환할 때 목록은 WinJS.Promise 개체가 결과를 처리하기 위해 지정한 함수에 전달됩니다.

JavaScript 프로미스를 만들고 취소 메커니즘을 설정하려면 asyncRun 및 asyncCancel 함수를 default.js에 추가합니다.

var resultAsync;
function asyncRun() {
    document.getElementById('output').innerHTML = "Retrieving prime numbers.";
    btnAsync.disabled = "disabled";
    btnCancel.disabled = "";

    resultAsync = SampleComponent.Example.getPrimesInRangeAsync(10000000000001, 2500).then(
        function (primes) {
            for (i = 0; i < primes.length; i++)
                document.getElementById('output').innerHTML += " " + primes[i];

            btnCancel.disabled = "disabled";
            btnAsync.disabled = "";
        },
        function () {
            document.getElementById('output').innerHTML += " -- getPrimesInRangeAsync was canceled. -- ";

            btnCancel.disabled = "disabled";
            btnAsync.disabled = "";
        },
        function (prog) {
            document.getElementById('primeProg').value = prog;
        }
    );
}

function asyncCancel() {    
    resultAsync.cancel();
}

asyncRun 함수는 비동기 GetPrimesInRangeAsync 메서드를 호출하여 WinJS.Promise 개체를 만듭니다. 개체의 then 메서드는 반환된 결과를 처리하는 세 함수를 사용하고 오류(취소 포함)에 대응하며 진행률 보고서를 처리합니다. 이 예제에서는 반환된 결과가 출력 영역에 인쇄됩니다. 취소하거나 완료하면 작업을 시작하고 취소하는 단추가 다시 설정됩니다. 진행률 보고는 진행률 컨트롤을 업데이트합니다.

asyncCancel 함수는 WinJS.Promise 개체의 cancel 메서드를 호출하기만 합니다.

앱을 실행하려면 F5 키를 선택합니다. 비동기 작업을 시작하려면 Async 단추를 선택합니다. 다음에 발생하는 상황은 컴퓨터 속도에 따라 다릅니다. 진행률 표시줄이 눈 깜짝할 사이에 완료로 이동하는 경우 GetPrimesInRangeAsync에 전달되는 시작 숫자의 크기를 10의 배수만큼 늘립니다. 테스트할 숫자의 개수를 늘리거나 줄여서 작업의 기간을 조정할 수 있지만 시작 숫자의 가운데에 0을 여러 개 추가하면 더 큰 영향을 미치게 됩니다. 작업을 취소하려면 Cancel Async 단추를 선택합니다.

참고 항목

개념

Windows 스토어 앱용 .NET 개요

Windows 스토어 앱용 .NET API

C# 및 Visual Basic으로 Windows Runtime 구성 요소 만들기

Async 및 Await를 사용한 비동기 프로그래밍(C# 및 Visual Basic)

Windows Runtime 구성 요소 만들기

Windows 런타임 구성 요소의 사용자 지정 이벤트 및 이벤트 접근자

기타 리소스

.NET Framework Support for Windows Store Apps and Windows Runtime