youtu.be/E17rD75Kn9Q

 

Visual Studio 2019 16.8 릴리즈 내용 중 몇가지를 소개하는 동영상입니다. 이 내용을 포함하는 블로그를 찾아봤는데 없어서 직접 작성하도록 하겠습니다.

 

Customizing your Visual Studio environment

. Themes

Visual Studio 2019에 다양한 테마를 테마를 적용할 수 있으며, 자신만의 테마를 만들 수도 있습니다.

 

Extensions -> Manage Extensions

Visual Studio 2019의 컬러 테마를 수정할 수 있는 확장입니다.

Visual Studio 2019의 Dark 테마와 비슷하지만 컬러가 좀 다른 테마입니다. 그 외에 theme로 검색하면 여러가지 테마가 조회가 되니 마음에 드시는 테마를 다운 받아서 설치하시면 될 것 같습니다.

테마를 변경하기 위해서는 Tools -> Options -> Environment -> General에서 변경하실 수 있습니다.

Visual Studio 2019의 전체 컬러도 그냥 검은색이 아니라 약간 다른 검은색으로 변경되면서 코드에 보이는 컬러도 변경됩니다. 여러가지를 변경해 봐야 마음에 드는 것 찾을 수 있을 것 같습니다.

 

. .NET Core Templates

새로운 프로젝트를 만들기 위해서 File -> New -> Project를 선택하고 프로젝트 템플릿 하나를 선택해서 프로젝트를 생성하게 됩니다. 그런데, 비슷한 프로젝트를 계속 만들려고 하면, 동일한 작업을 매번 반복하게 됩니다. 

그래서, 기본적인 프로젝트 구성을 완료하고 그 상태를 커스텀 템플릿으로 만드는 기능이 추가되었습니다.

 

Project -> Export Template...

Next 버튼을 클릭합니다.

템플릿 이름을 정하고, 아이콘 파일을 선택하고 Finish를 클릭합니다. 완료되면, 템플릿이 만들어진 폴더가 열립니다. 이제 프로젝트를 만들어 보도록 하겠습니다.

검색하는데 엄청 시간이 걸리기는 했지만 검색이 가능합니다. 몇 분만 참으시면 됩니다.

프로젝트를 생성했는데, 제가 입력했던 내용들과 Nuget package들까지도 모두 정상적으로 만들어집니다.

오오~ 이 기능 마음에 듭니다. 매번 동일한 작업을 반복하는 것이 매우 싫었는데, 이제라도 추가되어서 참 좋은 것 같습니다. 하지만, 멀티 프로젝트는 지원이 않됩니다. 즉, Uno Platform은 템플릿을 만들 수 없는 것 같습니다. ㅜㅜ 추후에는 솔루션 단위로 가능하면 좋겠습니다.

 

. Solution Filters

규모가 큰 솔루션은 매우 많은 프로젝트를 포함하고 있습니다. 이런 솔루션 파일을 열게되면, Visual Studio 2019에서 모든 프로젝트를 로드하는데 시간이 오래 걸립니다. 그런데, 이제는 모든 프로젝트를 로드할 필요가 없습니다.

 

File -> Open Project/Solution

솔루션 열기 다이얼로그 창 하단에 Do not load projects라는 체크 박스가 있습니다. 이 체크박스를 체크하고 솔루션 파일을 선택하면 프로젝트를 로드하지 않은 상태로 시작됩니다.

모든 프로젝트가 언로드 상태로 솔루션이 열렸습니다.

내가 로드할 프로젝트만 선택한 후 Reload project를 선택합니다. 그렴 딱 필요한 프로젝트만 열 수 있습니다. 그리고, 솔루션 아이템을 선택하고 마우스 오른쪽을 누른 후 Hide Unloaded Projects 메뉴를 선택합니다.

그러면, Unloaded projects들은 보이지 않습니다. 마지막으로, 솔루션 아이템에서 마우스 오른쪽 클릭을 다시하고 메뉴에서 Save as Solution Filter를 선택 합니다.

이름을 입력하고 Save를 클릭합니다.

이제 솔루션이 있는 폴더에 UnoContoso_UWP_Only.slnf 라는 파일이 생성되었습니다. 그리고, 이 파일을 Visual Studio 2019로 열면 저장한 상태로 솔루션이 Open됩니다. 

이 기능은 정말 최고입니다. 더 자세한 사항은 여기를 참고 합니다.

 

. Configurable File Nesting

ASP.NEt Core 프로젝트에서만 사용 가능한 기능으로 솔루션에서 관련 파일을 중첩하여 구성하고 찾을 수 있도록 지원하는 기능입니다. 자세한 사항은 여기를 참고 합니다.

 

//아래는 몇일 후에 작업 완료 하겠습니다. 다시 찾아주세요~

Productivity tools

. IntelliCode Team Completions

IntelliCode의 기본 모델 제안은 오픈 소스 GitHub 저장소에서 학습 한 패턴만을 기반으로하기 때문에 만약 고유 한 유형 또는 도메인 별라이브러리를 사용하는 경우에는 제안이 유용하지 않을 수 있습니다. 이러한 경우에 Visual Studio에서 C# 및 C++ 사용자가 직접 IntelliCode를 사용하여 코드에서 패턴을 학습시켜 코드에 맞는 제안을 만들 수 있습니다.

 

이 항목에 대한 더 자세한 사항은 여기를 참고 합니다.

. IntelliCode Suggestions

여러 위치에 유사한 편집을 수행 할 때 도움이 되는 기능이 추가되었습니다. 사용자가 비슷한 코드를 반복적으로 수정하는 경우에 그 내용을 추적하고 반복을 감지합니다. 그런 다음 적용 할 수 있는 다른 위치에 동일한 편집을 적용 할 것을 제안하게 됩니다.

이 기능은 Visual Studio 2019의 Quick Fix(Ctrl+.) 기능을 이용해서 사용할 수 있습니다.

세션에서 반복되는 편집을 통해 학습하기 때문에 IntelliCode Suggestion은 때때로 의도에 맞지 않는 제안을 합니다. 그런 경우에는 제안 무시를 선택하시면 됩니다.

이 기능에 대한 더 자세한 사항은 여기를 참고합니다.

. Web Live View

ASP.NET에 Xamarin Hot Reload 기능과 같은 실행 상태에서 HTML, CSS 등을 수정해서 화면에서 바로 확인할 수 있는 기능이 추가되었습니다. 이 기능을 이용하기 위해서는 Extension에서 web live로 검색을 하신후 선택해서 설치하시면 됩니다.

이 Web Live Preview 기능에 대한 자세한 사항은 여기를 참고 합니다.

 

'Visual Studio' 카테고리의 다른 글

What's New in Visual Studio 2019 version 16.8  (0) 2020.11.26
Welcome to C# 9.0  (0) 2020.11.24
Announcing .NET 5.0 RC1  (0) 2020.10.08
What's new in ML.NET  (0) 2020.06.17
Modernizing .NET Desktop Applications with .NET Core  (0) 2019.10.07
What's New In C# 8.0 Part 2  (0) 2019.09.30
블로그 이미지

kaki104

This blog covers the latest technologies in Microsoft .Net. In 2020, I will be talking about Uno Platform frequently. http://youtube.com/FutureOfDotNet https://twitter.com/kaki104

댓글을 달아 주세요

Welcome to C# 9.0

Visual Studio 2020. 11. 24. 21:25

C# 9.0이 릴리즈 되었습니다. 어떤 기능이 추가되었는지 블로그 포스트를 참고해서 간단하게 작성해 보았습니다.

 

C# 9.0 on the record | .NET Blog (microsoft.com)

 

C# 9.0 on the record | .NET Blog

C# 9.0 on the record It’s official: C# 9.0 is out! Back in May I blogged about the C# 9.0 plans, and the following is an updated version of that post to match what we actually ended up shipping. With every new version of C# we strive for greater clarity

devblogs.microsoft.com

Init-only properties

일반적으로 객체를 생성하는 간단한 방법이 있습니다. 특히 이 방법은 전체 객체 트리를 한번에 생성할 수 있어서 매우 유용합니다.

var person = new Person { FirstName = "Mads", LastName = "Torgersen" };

객체를 만들 클래스는 몇개의 간단한 속성을 가지고 있습니다

public class Person
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

그런데, 여기에는 큰 제한이 있습니다. 객체 생성시 데이터를 입력하기 위해서는 프로퍼티의 속성이 변경 가능해야 한다는 것입니다. 그래서, 초기화 전용 속성인 init를 추가했습니다.

public class Person
{
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
}

이 방법을 사용하면 위의 클라이언트 코드는 여전히 유효하지만 프로퍼티에 추후 값을 변경할때는 오류가 발생합니다.

var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // ERROR!

따라서, 초기화 전용 속성은 초기화가 완료되면 객체의 상태 변화를 막을 수 있습니다.

Init accessors and readonly fields

init 접근자는 초기화 중에만 호출 할 수 있기 때문에 클래스의 읽기 전용 필드를 변경할 수 있습니다.

public class Person
{
    private readonly string firstName = "<unknown>";
    private readonly string lastName = "<unknown>";
    
    public string FirstName 
    { 
        get => firstName; 
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName 
    { 
        get => lastName; 
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

Records

고전적인 객체 지향 프로그래밍의 핵심은 객체가 강력한 정체성을 가지고 있으며, 시간이 지남에 따라 진화하는 가변 상태를 캡슐화한다는 아이디어입니다. C#은 항상 이를 잘 수행했지만, 때로는 정반대를 원할 때가 있습니다. 여기서 C#의 기본값은 작업을 방해하는 경향이있어, 작업이 매우 힘들어졌습니다. 전체 객체가 불변하고 값처럼 동작하기를 원하면 이를 Records로 선언하는 것을 고려해야 합니다.

public record Person
{
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
}

record는 여전히 클래스이지만, record 키워드는 몇가지 추가 값 유사 동작을 포함합니다. 일반적으로 기록은 identity가 아닌 value로 정의됩니다. 이와 관련하여 record는 구조체에 훨씬 더 가깝지만 record는 여전히 reference type입니다.

record는 변경 가능하지만 주로 변경 불가능한 데이터 모델을 더 잘 지원합니다.

With-expressions

불변 데이터로 작업 할 때 일반적인 패턴은 기존 값에서 새 값을 만들어 새 상태를 나타내는 것입니다. 예를 들어, 우리가 성을 변경하는 경우 다른 성이있는 경우를 제외하고 이전 개체의 복사 본을 만들어 새 개체를 사용합니다. 이 기술을 종종 비파괴 돌연변이라고 합니다. 시간이 지남에 따라 사람을 나타내는 대신 record는 주어진 시간에 사람의 상태를 나타냅니다. 이러한 스타일의 프로그래밍을 돕기 위해 record는 새로운 종류의 표현인 with를 허용합니다.

var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
var otherPerson = person with { LastName = "Torgersen" };

With-expression은 객체 초기화 구분을 사용하여 이전 객체와 새 객체의 차이점을 표현합니다. 여러 속성을 지정할 수도 있습니다.

 

With-expression은 실제로 이전 객체의 전체 상태를 새 객체에 복사 한 다음 객체 초기화를 하는 방식으로 작동합니다. 즉, 프로퍼티에는 with-expression에서 변경 될수 있는 init 또는 set 접근자가 있어야 합니다.

Value-based equality

모든 객체는 객체 클래스에서 가상 Equals(object) 메서드를 상속합니다. 두 매개 변수가 모두 null이 아닌 경우 Object.Equals(object, object) 정적 메서드의 기반으로 사용됩니다. 구조체는 "값 기반 같음"을 가지도록 재정의하고 Equals를 재귀적으로 호출하여 구조체의 각 필드를 비교합니다. record도 마찬가지로 동작합니다. 이는 "value-ness"에 따라 두 record 객체가 동일한 객체가 아니어도 서로 동일 할 수 있을을 의미 합니다. 예를 들어 수정 된 사람의 성을 다시 수정하는 경우 :

var originalPerson = otherPerson with { LastName = "Nielsen" };

이제 ReferenceEquals(person, originalPerson) = false이지만 Equals(person, originalPerson) = true가 됩니다. 값 기반 Equals와 함께 값 기반 GetHashCode() 재정의도 있습니다. 또한 record는 IEquatable<T>를 구현하고 == 및 != 연산자를 오버로드하므로 값 기반 동작이 모든 서로 다른 같음 메커니즘에서 일관되게 표시됩니다.

Inheritance

Record는 다른 record에서 상속 할 수 있습니다.

public record Student : Person
{
    public int ID;
}

With-expression 및 value equality는 정적으로 알려진 유형 뿐만 아니라 전체 런타임 객체를 고려한다는 점에서 record 상속과 잘 작동합니다. Student를 생성하지만 Person 변수에 저장한다고 가정합니다.

Person student = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 129 };

With-expression은 여전히 전체 객체를 복사하고 런타임 유형을 유지합니다.

var otherStudent = student with { LastName = "Torgersen" };
WriteLine(otherStudent is Student); // true

같은 방식으로 값이 같으면, 두 객체의 런타임 유형이 동일한지 확인한 다음 모든 상태를 비교합니다.

Person similarStudent = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 130 };
WriteLine(student != similarStudent); //true, since ID's are different

Positional records

위치적인 접근 방식이 유용할 때가 있습니다. 여기서 그 내용은 생성자 인수를 통해 제공되고 위치 분해를 통해 추출 할 수 있습니다. record에서 자체 생성자와 해체자를 지정하는 것은 완벽하게 가능합니다.

public record Person 
{ 
    public string FirstName { get; init; } 
    public string LastName { get; init; }
    public Person(string firstName, string lastName) 
      => (FirstName, LastName) = (firstName, lastName);
    public void Deconstruct(out string firstName, out string lastName) 
      => (firstName, lastName) = (FirstName, LastName);
}

그러나, 정확히 동일한 것을 표현하기 위한 훨씬 더 짧은 구문이 있습니다.

public record Person(string FirstName, string LastName);

이것은 공용 초기화 전용 자동 속성과 생성자 및 해체자를 선언하므로 다음과 같이 사용할 수 있습니다.

var person = new Person("Mads", "Torgersen"); // positional construction
var (f, l) = person;                        // positional deconstruction

생성 된 자동 속성이 마음에 들지 않으면 같은 이름의 고유 한 속성을 대신 정의 할 수 있으며, 생성 된 생성자와 해체자는 해당 속성을 사용합니다. 이 경우 매개 변수는 초기화에 사용할 수 있는 범위에 있습니다. 예를 들어 FirstName을 보호 된 속성으로 사용하고 싶다고 가정 해 보겠습니다.

public record Person(string FirstName, string LastName)
{
    protected string FirstName { get; init; } = FirstName; 
}

Positional record는 다음과 같은 기본 생성자를 호출 할 수 있습니다.

public record Student(string FirstName, string LastName, int ID) : Person(FirstName, LastName);

 

Top-level programs

C#으로 간단한 프로그램을 작성하려면, 상당한 양의 상용구 코드가 필요합니다.

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

이것은 언어 초보자에게 어렵고 느끼게하고, 코드를 복잡하게 만드는 들여 쓰기 수준을 추가하게 됩니다. 그래서, C# 9.0에서는 Top-level 에서 기본 프로그램을 작성할 수 있습니다.

using System;

Console.WriteLine("Hello World!");

모든 코드가 허용됩니다. 프로그램은 using 후와 파일의 유형 또는 네임 스페이스 선언 전에 작성되어야하며, Main 메서드가 하나만 있을 수 있는 것처럼 하나의 파일에서만 그 작업을 수행 할 수 있습니다. 상태코드를 반환하려면, 그렇게 할 수 있습니다. 작업을 기다리고 싶다면, 그렇게 할 수 있습니다. 명령 줄 인수에 액서스하려면 args를 사용할 수 있습니다.

using static System.Console;
using System.Threading.Tasks;

WriteLine(args[0]);
await Task.Delay(1000);
return 0;

로컬 함수는 명령문의 한 형태이며 Top-level 프로그램에서도 허용됩니다. 대신 외부에서 호출하는 것은 오류를 발생합니다.

 

Improved pattern matching

몇 가지 새로운 종류의 패턴이 C#9.0에 추가되었습니다. 패턴 일치 튜토리얼의 이 코드를 살펴 보도록 하겠습니다. 자세한 사항은 여기를 참고하세요.

public static decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
       ...
       
        DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
        DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m,
        DeliveryTruck _ => 10.00m,

        _ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
    };

Simple type patterns

이전에는 형식 패턴이 형식이 일치 할 때 식별자를 선언해야 했습니다. 위의 DeliveryTruck _에서와 같이 해당 식별자가 폐기 _ 인 경우에도 마찬가지입니다. 그러나 이제는 아래와 같이 사용할 수 있습니다.

DeliveryTruck => 10.00m,

Relational patterns

C#9.0에는 관계 연산자 <, <= 등에 해당하는 패턴이 도입되었습니다. 따라서 이제 위 패턴의 DeliveryTruck 부분을 중첩 된 스위치 표현식으로 작성할 수 있습니다.

DeliveryTruck t when t.GrossWeightClass switch
{
    > 5000 => 10.00m + 5.00m,
    < 3000 => 10.00m - 2.00m,
    _ => 10.00m,
},

Logical patterns

마지막으로, 식에 사용되는 연산자와의 혼동을 피하기 위해 패턴을 논리 연산자와 결합 할 수 있습니다. 예를 들어, 위의 중첩 스위치의 경우는 다음과 같이 오름차순으로 배치 될 수 있습니다.

DeliveryTruck t when t.GrossWeightClass switch
{
    < 3000 => 10.00m - 2.00m,
    >= 3000 and <= 5000 => 10.00m,
    > 5000 => 10.00m + 5.00m,
},
not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))

또한 다루기 힘든 이중 괄호 대신 is-expression을 포함하는 if 조건에서 편리하게 사용할 수 있습니다.

if (!(e is Customer)) { ... }

상당히 불편하게 느꼈던 부분을 아래 처럼 변경할 수 있습니다.

if (e is not Customer) { ... }

실제로 is not expression은 Customer와 같은 type을 비교할 때도 사용할 수 있습니다.

if (e is not Customer c) { throw ... } // if this branch throws or returns...
var n = c.FirstName; // ... c is definitely assigned here

 

Target-typed new expressions

"Target-typing"은 표현식이 사용되는 컨텍스트에서 유형을 가져올 때 사용하는 용어입니다. 예를 들어 null 및 람다식은 항상 대상 형식입니다. C#의 새 식에는 항상 형식을 지정해야합니다. C# 9.0에서는 표현식이 할당되는 명확한 유형이 있는 경우 유형을 생략 할 수 있습니다.

Point p = new (3, 5);

이것은 배열이나 객체 초기화에서 반복이 많은 경우 매우 좋습니다.

Point[] ps = { new (1, 2), new (5, 2), new (5, -3), new (1, -3) }; 

Covariant returns

파생 클래스의 메서드 재정의에 기본 형식의 선언 보다 더 구체적인 반환 형식이 있음을 나타내는 것이 유용할 때가 있습니다. C# 9.0에서는 다음이 가능합니다.

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

And much more…

C# 9.0 기능의 전체 세트를 확인하기 위해 가장 좋은 곳은 "C# 9.0의 새로운 기능" 문서 페이지 입니다.

 

'Visual Studio' 카테고리의 다른 글

What's New in Visual Studio 2019 version 16.8  (0) 2020.11.26
Welcome to C# 9.0  (0) 2020.11.24
Announcing .NET 5.0 RC1  (0) 2020.10.08
What's new in ML.NET  (0) 2020.06.17
Modernizing .NET Desktop Applications with .NET Core  (0) 2019.10.07
What's New In C# 8.0 Part 2  (0) 2019.09.30
블로그 이미지

kaki104

This blog covers the latest technologies in Microsoft .Net. In 2020, I will be talking about Uno Platform frequently. http://youtube.com/FutureOfDotNet https://twitter.com/kaki104

댓글을 달아 주세요

Uno Platform Uno Platform 3.3 - Day 0 support for WinUI 3 Preview 3, Android 11

Uno Platform 3.3이 릴리즈 되었습니다.

 

조만간 정식 릴리즈가될 WinUI 3 Preview를 지원을 추가했습니다. 

 

Uno Platform을 처음 사용하는 사용자를 위해 - WebAssembly를 이용한 Windows, iOS, Android, macOS, Linux 및 웹에서 기본적으로 실행되는 픽셀 단위의 단일 소스 C# 및 XAML 앱을 만들 수 있습니다. Uno Platform은 무료이며 오픈 소스 (Apach 2.0)입니다. Uno Platform을 시작 하기 위해서는 여기를 클릭하세요

 

WinUI 3 Preview 3 support

 

WinUI Desktop 프로젝트 템플릿에 대한 지원을 추가했습니다. 데스크톱 템플릿을 사용하면 WinUI 응용 프로그램이 UWP 샌드 박스에서 실행하지 않고도, .NET 5와 함께 전체 Win32 및 WinRT API를 사용할 수 있습니다.

 

WinUI 3 API를 사용하여 Uno Platform 애플리케이션을 생성하려면 다음 명령을 실행하세요.

dotnet new -i Uno.ProjectTemplates.Dotnet
dotnet new unoapp-winui -o MyApp

dotnet 템플릿에 대한 최신 정보를 여기서 확인 할 수 있습니다.

 

WinUI 3 Preview 지원에 대한 몇 가지 사항

 

. Windows 데스크톱 프로젝트 디버깅 및 실행은 "MyProject.Windows.Desktop" 패키지가 아닌 "MyProject.Windows.Package" 프로젝트를 사용하여 수행해야 합니다.(그렇지 않으면 시작시 오류 발생)

. 템플릿으로 만든 WinUI + UWP 프로젝트는 아직 .NET 5를 지원하지 않지만 C# 8.0 및 .NET Standard 2.0 라이브러리를 지원합니다.

. WinUI 3 Windows 데스크톱 및 UWP 헤드를 실행하려면 Visual Studio 2019 16.9 Preview 1을 설치하는 것이 가장 좋습니다. 다른 프로랫폼의 경우 Visual Studio 2019의 안정적인 최신 빌드를 선택할 수 있습니다.

 

Android 11 and iOS 14 Support

. 현재 Android 11 지원을 제공하고 있으며 11월 1일부터 Google에서 지원ㅇ르 중단 한 Android 9 사용을 중단 합니다.

 

Support for KeyUp and KeyDown Skia backends

키보드는 커뮤니티의 큰 요청이었으며 이 기능의 첫번째 헤택은 Linux용 Uno 계산기입니다. 숫자 및 연산자 입력에 대한 키보드 지원을 사용하도록 Snapped 애플리케이션을 업데이트했습니다.

 

또한 Snap를 지원하지 않는 배포또한 Snap을 지원하지 않는 배포판을 사용 중이거나 Snap이 설치되지 않은 경우 Linux 계산기에 대한 AppImage 지원을 업데이트했습니다.

 

How to Upgrade

Uno Platform의 최신 릴리스로 업그레이드하려면 Visual Studio NuGet 패키지 관리자를 통해 패키지를 3.3으로 업데이트하십시오! Uno Platform을 처음 사용하는 경우 시작하는 가장 좋은 방법은 공식 시작 안내서를 따르는 것입니다.

 

Webinar: Uno Platform and .NET 5 (Dec 17th, 2020, Noon EST)

Uno Platform에서 .NET 5에 대한 지원을 추가하면 성능을 향상시키고 솔루션 복잡성을 줄일 수있는 여러 기회가 생성됩니다. .NET 5를 사용하면 기존 Microsoft Windows 대상뿐 아니라 WebAssembly, Linux, macOS, iOS 및 Android도 대상으로 더 잘 지정할 수 있습니다. Uno Platform이 .NET 5 및 WinUI를 활용하는 방법에 대한 30 분 개요와 CTO Jerome Laban과의 실시간 Q & A에 참여하십시오.

블로그 이미지

kaki104

This blog covers the latest technologies in Microsoft .Net. In 2020, I will be talking about Uno Platform frequently. http://youtube.com/FutureOfDotNet https://twitter.com/kaki104

댓글을 달아 주세요

.NET Conf 2020가 마무리 되었습니다.

많은 양의 동영상과 Blog 포스팅이 쏟아져 나와서 동영상을 보거나 읽기도 빠듯하네요

우선 .NET Conf 2020관련 유튜브 동영상 중 C#이나 제가 관심있어서 보려고 하는 동영상만 정리를 했습니다. 전체 동영상은 81개인데... 줄이고 줄이고 줄여서 16정도 보고 싶네요..ㅎㅎ

 

.NET Conf 2020 전체 재생 목록

 

1. Keynote - Welcome to .NET 5 (youtu.be/o-esVzL3YLI)

2. What’s New in C#? 

3. A talk for trailblazers: Blazor in .NET 5

4. Porting Projects to .NET 5

5. Entity Framework Core 5.0: The Next Generation for Data Access

6. What's new for desktop developers building WPF, UWP & WinForms

7. Developer Fun with Scott Hanselman

8. What’s New in Visual Studio 2019 and beyond

9. Asynchronous Courotines with C#

10. Get a Head Start with Entity Framework Core 5.0 with EF Core Power Tools

11. Microfrontends with Blazor: Welcome to the Party!

12. Introducing the MVVM Toolkit, a .NET Standard Library in the Windows Community Toolkit

13. Overview of Single File Applications in .NET 5

14. Building Native Android Apps with .NET

15. Build native and hybrid mobile apps with Mobile Blazor Bindings

16. Build Real Embedded IoT with C# using Meadow

 

'.NET 5 and .NET Conf 2020' 카테고리의 다른 글

.NET Conf 2020 Youtube  (0) 2020.11.19
블로그 이미지

kaki104

This blog covers the latest technologies in Microsoft .Net. In 2020, I will be talking about Uno Platform frequently. http://youtube.com/FutureOfDotNet https://twitter.com/kaki104

댓글을 달아 주세요

2020년은 오프라인 비지니스 환경에서 언텍트 비지니스 환경으로 변화하는 변곡점이라고 있을 같습니다. 그에 따라 이제는 PC 뿐만 아니라 다양한 모바일 디바이스에서 실행되는 크로스플랫폼 개발 환경이 더욱더 발전을 것이라고 생각됩니다.

 

2018 5 최초로 공개된 Uno Platform 크로스 플랫폼에서 실행되는 비지니스 응용 프로그램을 개발하는 가장  빠른 방법이라고 생각하며, 포스트에서 Microsoft Contoso UWP앱을 Uno Platform Prism으로 포팅할 참고할 사항이나 필요한 기술에 대해서 이야기를 하려고 합니다. Uno Platform 대한 자세한 사항은 여기 참고하시기 바랍니다.

 

Contoso UWP app

https://github.com/microsoft/Windows-appsample-customers-orders-database

UnoContoso app – Uno Platform

Uno.Samples/UI/UnoContoso at master · unoplatform/Uno.Samples (github.com)

 

포팅시 어려웠던 부분?

  1. Entity Framework Core 버전이 2.1에서 3.x 버전으로 변경되면서 발생한 문제를 해결하는데  시간이 오래 걸렸습니다. 변경 사항은 여기 참고하시기 바랍니다.
  2. Sqlite Entity Framework Core 이용해서 앱에서 직접 접근해서 사용하는 방식이 Wasm, iOS, macOS 등에서 원활하지 않아서, Web API 통해서만 사용할 있도록 수정하는데 시간이 걸렸습니다.
  3. Contoso UWP앱이 Event-driven 방식을 사용하고 있어서, 부분을 MVVM Pattern으로 변경하는 부분도 시간이 걸렸습니다.

프로젝트에 대한 간단한 설명

  • UnoContoso.Droid : 안드로이드 해더 프로젝트
  • UnoContoso.iOS : iOS 헤더 프로젝트
  • UnoContoso.macOS : macOS 헤더 프로젝트
  • UnoContoso.Uwp : Uwp 헤더 프로젝트
  • UnoContoso.Wasm : Wasm 헤더 프로젝트
  • UnoContoso.Models : 모델 프로젝트, .NET Standard 2.0
  • UnoContoso.Repository : 레파지토리 프로젝트, .NET Standard 2.0
  • UnoContoso.Service : 서비스 프로젝트, .NET Core 3.1
  • UnoContoso.Shared : 공유 프로젝트

Tips

  • NavigationView
    • AlwaysShowHeader=”False” :상단에 헤더를 표시하지 않습니다. 헤더를 표시하는 보다 표시 하지 않는 것이 아래의 이유로 인해서 개발이 용이 합니다.
      • iOS 경우 헤더의 위치가 다른 플랫폼과 다르게 위로 올라가서 표시됩니다.
      • Windows, Wasm, macOS에서는 헤더 위치에 CommandBar 표시하고, Android, iOS에서는 화면 하단에 표시하기 때문에, NavigationView.HeaderTemplate 사용이 어렵습니다.
    • NavigationViewBehavior : NavigationView 표시되는 메뉴를 동적으로 구현하고, 선택된 Menu ViewModel 바인딩하기 위해서 Behavior 만들었습니다. Behavior 일반적인 사항은 여기 참고하시기 바랍니다.
    • ContentControl : Prism에서 일반적으로 Region 지정하는 컨트롤입니다. 컨트롤을 통해서 View 네비게이션 됩니다. 자세한 사항은 Prism RegionNavigation 참고하시기 바랍니다.
      • 컨트롤의 Padding 화면의 기본 Margin입니다.
      • iOS 경우 ios:Padding=”10,20,0,0” 사용해야 합니다.
<!--Code1-->
        <NavigationView
            IsBackButtonVisible="Collapsed"
            OpenPaneLength="160"
            IsSettingsVisible="False"
            AlwaysShowHeader="False"
            IsTabStop="False">
            <i:Interaction.Behaviors>
                <behaviors:NavigationViewBehavior 
                    MenuItems="{Binding Menus}"
                    SelectedMenuItem="{Binding SelectedItem, Mode=TwoWay}"/>
            </i:Interaction.Behaviors>
            <ContentControl 
                prismRegions:RegionManager.RegionName="ContentRegion" 
				HorizontalContentAlignment="Stretch"
				VerticalContentAlignment="Stretch"
                not_ios:Padding="10,0,0,0"
                ios:Padding="10,20,0,0"/>
        </NavigationView>
  • x:Bind
    • Android iOS 앱의 성능 향상을 위해서 x:Bind 주로 사용했습니다.
    • x:Bind 사용하기 위해서 code behind에서ViewModel이라는 프로퍼티를 추가합니다. (Code2-1)
    • x:Bind 기본 mode OneTime입니다. 그래서, 프로퍼티의 데이터가 변경되는 곳이라면 mode=OneWay 반드시 추가해야 합니다.
    • 컨트롤의 이벤트와 뷰모델의 메소드를 x:Bind 직접 연결하는 방법은 사용할 없습니다.
    • IValueConverter 이용하는 경우 Android에서  x:Bind 사용할 없기 때문에, Binding 사용합니다. (Code2-2)
//Code2-1
public CustomerListViewModel ViewModel
{
    get { return DataContext as CustomerListViewModel; }
}
<!--Code2-2-->
                <TextBlock
                    Grid.Column="0"
                    Margin="0" Padding="0"
                    Text="{x:Bind Product.Name}" />
                <TextBlock
                    Grid.Column="2"
                    Margin="0" Padding="0"
                    HorizontalAlignment="Right"
                    Text="{Binding Product.ListPrice,
                        Converter={StaticResource StringFormatConverter},
                        ConverterParameter='{}{0:n}'}" />
  • Prism
    • View ViewModel 자동으로 연결해 줍니다. prismMvvm:ViewModelLocator.AutowireViewModel="True" 코드를 참고 합니다. 자세한 사항은 여기를 참고 합니다.(Code3-1)
    • RegionNavigation 사용하기 위해서는 App.xaml.cs → void RegisterTypes() 메소드에 RegisterForNavigation 이용해서 등록해야 합니다. (Code3-2)
    • DialogService 사용하는 화면은 View ViewModel 수동으로 연결 합니다. App.xaml.cs -} void RegisterTypes() 메소드에 있는 코드를 참고 합니다.   자세한 사항은 여기 참고 합니다. (Code3-3)
    • DelegateCommand ObservesProperty 이용하면, Command 사용여부를 쉽게 변경할 있습니다. 자세한 사항은 여기 참고 합니다. (Code3-4)
    • EventAggregator 이용하면 뷰모델과 뷰모델 사이에 커뮤니케이션을 있습니다. 이는 Loosely Coupled 연결입니다. 자세한 사항은 여기 참고 합니다. (Code3-5)
<!--Code3-1-->
<UserControl
    x:Class="UnoContoso.Views.OrderDetailView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UnoContoso.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="using:Microsoft.Xaml.Interactivity" 
    xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
    xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls"
    xmlns:prismMvvm="using:Prism.Mvvm" 
    prismMvvm:ViewModelLocator.AutowireViewModel="True"
    xmlns:uc="using:UnoContoso.UserControls" 
    xmlns:stateTriggers="using:UnoContoso.StateTriggers" 
    xmlns:models="using:UnoContoso.Models" 
    x:Name="Root"
    mc:Ignorable="d">
//Code3-2
containerRegistry.RegisterForNavigation<CustomerListView>();
containerRegistry.RegisterForNavigation<CustomerDetailView>();
containerRegistry.RegisterForNavigation<HomeView>();
containerRegistry.RegisterForNavigation<OrderListView>();
containerRegistry.RegisterForNavigation<OrderDetailView>();
//Code3-3
containerRegistry.RegisterDialog<MessageControl, MessageViewModel>();
containerRegistry.RegisterDialog<ConfirmControl, ConfirmViewModel>();
//Code3-4
            ViewDetailCommand = new DelegateCommand(OnViewDetail,
                () => SelectedCustomer != null)
                .ObservesProperty(() => SelectedCustomer);
//Code3-5
Step 1. CustomerListViewModel.cs
            EventAggregator.GetEvent<CustomerEvent>()
                .Subscribe(ReceivedCustomerEvnet, false);

Step 2. CustomerDetailViewModel.cs
            EventAggregator.GetEvent<CustomerEvent>()
                .Publish(new EventArgs.CustomerEventArgs 
                { 
                    Changes = change,
                    Customer = Customer.Model
                });
                
Step 3. CustomerListViewModel.cs
        private void ReceivedCustomerEvnet(CustomerEventArgs obj)
        {
            switch (obj.Changes)
            {
                case Enums.EntityChanges.None:
                    break;
                case Enums.EntityChanges.Changed:
                    {
                        var customer = _allCustomers
                            .FirstOrDefault(c => c.Model.Id == obj.Customer.Id);
                        if (customer == null) return;
                        customer.Model = obj.Customer;
                        customer.UpdateProperty();
                    }
                    break;
                case Enums.EntityChanges.Added:
                    {
                        var customer = new CustomerWrapper(_contosoRepository, obj.Customer);
                        _allCustomers.Add(customer);
                    }
                    break;
                case Enums.EntityChanges.Deleted:
                    {
                        var customer = _allCustomers
                            .FirstOrDefault(c => c.Model.Id == obj.Customer.Id);
                        if (customer == null) return;
                        _allCustomers.Remove(customer);
                    }
                    break;
            }
        }
  • RelativePanel
    • 컨트롤을 관계에 의해 배치 합니다. 자세한 사항은 여기 참고 하시기 바랍니다.
    • VisualState 이용해서 상단에 위치한 CommandBar 위치를 하단으로 변경할 CommandBar 컨트롤에 RelativePanel.LeftOf RelativePanel.RightOf 입력되어있는 이름을 지워주어야 하단으로 이동이 가능합니다. 부분은 UWP에서의 동작과는 다르게 동작합니다.
<!--Code4-->
<VisualState>
  <VisualState.StateTriggers>
    <stateTriggers:MobileScreenTrigger />
  </VisualState.StateTriggers>
  <VisualState.Setters>
    <Setter Target="mainCommandBar.(RelativePanel.AlignBottomWithPanel)" Value="True" />
    <Setter Target="mainCommandBar.(RelativePanel.AlignLeftWithPanel)" Value="True" />
    <Setter Target="mainCommandBar.(RelativePanel.AlignRightWithPanel)" Value="True" />
    <Setter Target="mainCommandBar.(RelativePanel.LeftOf)" Value="" />
    <Setter Target="mainCommandBar.(RelativePanel.RightOf)" Value="" />
    <Setter Target="mainCommandBar.HorizontalAlignment" Value="Stretch" />
    <Setter Target="PageTitle.Margin" Value="30,4,0,0"/>
    <Setter Target="CustomerSearchBox.Width" Value="240" />
    <Setter Target="RootDataGrid.Margin" Value="0,10,0,40" />
    <Setter Target="CustomerListMobileView.Visibility" Value="Visible"/>
    <Setter Target="CustomerDataGrid.Visibility" Value="Collapsed"/>
  </VisualState.Setters>
</VisualState>
  • CommandBar
    • 커맨드 버튼을 배치하는 컨트롤 입니다. 기본적인 사항은 여기 참고하시기 바랍니다.
    • CommandBarBehavior 크기가 변하면 DefaultLabelPosition Right에서 Bottom으로 이동 시키기 위해서 추가한 Behavior입니다.
    • DefaultLabelPosition 프로퍼티가 Uno에서 미구현 프로퍼티이기 때문에, 대부분의 플랫폼에서는 기본값 Bottm 가지고 있습니다.
<!--Code5-->
            <CommandBar 
                x:Name="mainCommandBar"
                Background="White"
                HorizontalAlignment="Right"
                DefaultLabelPosition="Right"
                RelativePanel.LeftOf="CustomerSearchBox"
                RelativePanel.RightOf="PageTitle">
                <i:Interaction.Behaviors>
                    <behaviors:CommandBarBehavior/>
                </i:Interaction.Behaviors>
                <AppBarButton
                    Icon="Contact"
                    Label="View details"
                    ToolTipService.ToolTip="View details" 
                    Command="{x:Bind ViewModel.ViewDetailCommand}"/>
                <!--skip lines-->
                <AppBarButton
                    Icon="Refresh"
                    Label="Sync"
                    ToolTipService.ToolTip="Sync with server" 
                    Command="{x:Bind ViewModel.SyncCommand}"/>
            </CommandBar>
  • CollapsibleSearchBox
    • UserControl 이용해서 만든 검색용 컨트롤입니다.
    • Uno에서 그래픽 아이콘은 SymbolIcon 컨트롤을 이용해야, 플랫폼에서도 정상적으로 아이콘이 출력됩니다.
<!--Code6-->
        <ToggleButton
            x:Name="searchButton"
            HorizontalAlignment="Right"
            VerticalAlignment="Top"
            Background="Transparent"
            Checked="SearchButton_Checked"
            Visibility="Collapsed"
            Padding="4">
            <SymbolIcon Symbol="Find" />
        </ToggleButton>
  • ListView
    • 모바일 디바이스에서는 화면에 출력되는 항목의 갯수와 가로 스크롤바의 유무에 따라서 세로 스크롤 성능에 영향을 미치기 때문에 DataGrid보다는 ListView 이용하는 것이 좋습니다. 자세한 사항은 여기 참고 합니다.
    • macOS에서는 아직 DataGrid 사용할 없기 때문에 ListView 사용합니다. 하지만, macOS ListView 컨트롤 내부에 ScrollView 없기 때문에 세로 스크롤이 불가능한 것으로 보입니다. macOS 지원은 점점 추가될 것으로 생각합니다.
  • DataGrid
    • Data Grid 형태로 보여주며, 수정도 가능한 컨트롤이며, Windows Community Toolkit 설치해야 사용할 있습니다. 자세한 사항은 여기 참고 합니다.
    • DataGridBehavior Sort기능과 마우스 오른쪽 버튼을 지원하기 위해서 만들었습니다.
    • 컬럼에 프로퍼티를 바인딩 할때 x:Bind 사용할 없습니다.
<!--Code7-->
<toolkit:DataGrid
    x:Name="OrderListDataGrid" BorderThickness="0" 
    CanUserReorderColumns="False" CanUserResizeColumns="False"
    GridLinesVisibility="None" IsReadOnly="True"
    AutoGenerateColumns="False" Margin="0,10,0,0"
    ItemsSource="{x:Bind ViewModel.Orders, Mode=OneWay}"
    SelectedItem="{x:Bind ViewModel.SelectedOrder, Mode=TwoWay}"
    ContextFlyout="{StaticResource DataGridContextMenu}">
	<i:Interaction.Behaviors>
	  <behaviors:DataGridBehavior/>
	</i:Interaction.Behaviors>
	<toolkit:DataGrid.Columns>
	  <toolkit:DataGridTextColumn
	    Header="Invoice" Tag="InvoiceNumber" Binding="{Binding InvoiceNumber}"/>
	  <toolkit:DataGridTextColumn
	    Header="Customer" Tag="CustomerName" Binding="{Binding CustomerName}"/>
	  <toolkit:DataGridTemplateColumn
	    Header="Date" Tag="DatePlaced">
	    <toolkit:DataGridTemplateColumn.CellTemplate>
	      <DataTemplate>
	        <TextBlock
	          VerticalAlignment="Center" Margin="12,0"
		   Text="{Binding DatePlaced, Mode=OneWay,
                     Converter={StaticResource StringFormatConverter},
                     ConverterParameter='{}{0:d}'}" />
	      </DataTemplate>
	    </toolkit:DataGridTemplateColumn.CellTemplate>
	  </toolkit:DataGridTemplateColumn>
         <!-- Skip lines -->
	  <toolkit:DataGridTextColumn
	    Header="Status" Binding="{Binding Status}"/>
	</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
  • VisualState
    • 특정 상태일 UI 요소의 시각적 모양을 지정할 있는 기능입니다. 자세한 사항은 여기 참고 합니다.
    • AdaptiveTrigger : 윈도우 속성을 기반으로 시각적 상태를 선언하는 트리거 입니다. 자세한 사항은 여기 참고 합니다.  
    • VisualState 이용해서 디자인 작업을 할 , 기본 크기에 대한 디자인은 화면에 왼쪽 상단에서8” Tablet(1280x800) 선택하고, 최소 크기에 대한 디자인은 6”Phone(1920x1080) 선택해서 작업하면 약간 편하게 작업할 있습니다.
    • 최소 크기인 경우 PageTitle 컨트롤의 왼쪽에 30 Margin 추가해서 햄버거 버튼과 겹치는 현상을 방지 합니다.
    • MobileScreenTrigger 윈도우 크기가 변경될 실행 중인 디바이스의 이름을 가지고 오고, Mobile이라는 이름을 포함하면 SetActive() 이용해서 VisualState 변경합니다. UIViewSettings.GetForCurrentView().UserInteractionMode Uno에서  미구현 상태라 주석 처리를 했습니다.
<!--Code8-->
<VisualStateManager.VisualStateGroups>
  <VisualStateGroup>
    <VisualState>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="{StaticResource LargeWindowSnapPoint}" />
      </VisualState.StateTriggers>
    </VisualState>
  <VisualState>
    <VisualState.StateTriggers>
      <AdaptiveTrigger MinWindowWidth="{StaticResource MediumWindowSnapPoint}" />
    </VisualState.StateTriggers>
  </VisualState>
  <VisualState>
    <VisualState.StateTriggers>
      <AdaptiveTrigger MinWindowWidth="{StaticResource MinWindowSnapPoint}" />
    </VisualState.StateTriggers>
    <VisualState.Setters>
      <Setter Target="PageTitle.Margin" Value="30,4,0,0"/>
    </VisualState.Setters>
  </VisualState>
  <VisualState>
    <VisualState.StateTriggers>
      <stateTriggers:MobileScreenTrigger />
    </VisualState.StateTriggers>
    <VisualState.Setters>
    <!-- Skip lines -->
    </VisualState.Setters>
  </VisualState>
  </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
  • BackButton
    • 뒤로가기 버튼입니다. GoBackCommand 커맨드는 ViewModelBase 선언되어 있습니다.
<!--Code9-->
            <Button 
                x:Name="BackButton"
                Style="{StaticResource NavigationBackButtonNormalStyle}"
                Command="{x:Bind ViewModel.GoBackCommand}"/>
  • TextBox
    • 일반 텍스트를 표시하고 편집하는 사용하는 컨트롤 입니다. 자세한 사항은 여기 참고 합니다.
    • Text 프로퍼티에 TwoWay바인딩을 때는 UpdateSourceTrigger=PropertyChanged 이용하는 것이 좋습니다. 왜냐하면, 내용을 수정하면 바로 바인딩된 프로퍼티가 수정되기 때문입니다. 기본값은 LostFocus입니다. 
<!--Code10-->
<TextBox
    x:Name="FirstName"
    MinWidth="120"
    Margin="0,8,16,8"
    Header="First name"
    IsReadOnly="{x:Bind ViewModel.Customer.IsInEdit, 
	    Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
    RelativePanel.AlignLeftWithPanel="True"
    Text="{x:Bind ViewModel.Customer.FirstName, Mode=TwoWay, 
	    UpdateSourceTrigger=PropertyChanged}" />
  • InvoiceTemplate (DataTemplate)
    • 데이터 개체의 시각적 구조를 만드는데 사용합니다. 자세한 사항은 여기 참고 합니다.
    • HyperlinkButton 1개가 존재하며 Command win not_win으로 나누어져 있습니다. 각 플랫폼별 xaml 코드를 적용하는 방법은 여기를 참고 합니다.
    • Windows에서는 win:Command="{Binding Source={StaticResource ViewModelElement}, Path=ViewModel.ViewInvoiceCommand}" 사용해야 Command 실행됩니다.
    • Windows 아닌 경우에는 not_win:Command="{Binding ElementName=DataGrid,                     Path=DataContext.ViewInvoiceCommand}" 사용해야 Command 실행됩니다.
    • Windows에서 ElementName=DataGrid 이용하는 방법이 않되는 이유는 모릅니다.
<!--Code11-->
<DataTemplate x:Key="InvoiceTemplate">
    <HyperlinkButton
        Content="{Binding InvoiceNumber}"
        Margin="12,0"
        win:Command="{Binding Source={StaticResource ViewModelElement}, 
            Path=ViewModel.ViewInvoiceCommand}"
        not_win:Command="{Binding ElementName=DataGrid, 
            Path=DataContext.ViewInvoiceCommand}"
        CommandParameter="{Binding}"/>
</DataTemplate>
  • Etc
    • 만약 솔루션에 .Net Standard 프로젝트를 포함하고, 사용한다면, Wasm 프로젝트의 LinkerConfig.xml 파일에 해당 내용을 추가해야 합니다.
<!—LinkerConfig.xml -->
<linker>
  <assembly fullname="UnoContoso.Wasm" />
  <assembly fullname="Uno.UI" />
  <assembly fullname="System.Core">
	<!-- This is required by JSon.NET and any expression.Compile caller -->
	<type fullname="System.Linq.Expressions*" />
  </assembly>
  <assembly fullname="UnoContoso.Models"/>
  <assembly fullname="UnoContoso.Repository"/>
</linker>

이상으로 작업을 하면서 알게된 몇가지 팁들을 정리했습니다. 앞으로도 좋은 정보가 있으면 꾸준히 올리도록 하겠습니다.

Facebook : https://www.facebook.com/kaki104

Twitter : https://twitter.com/kaki104

Youtube : http://youtube.com/FutureOfDotNet

 

블로그 이미지

kaki104

This blog covers the latest technologies in Microsoft .Net. In 2020, I will be talking about Uno Platform frequently. http://youtube.com/FutureOfDotNet https://twitter.com/kaki104

댓글을 달아 주세요