[C# 11 과 .NET 7] 7. .NET Packaging과 배포
1. .NET 7
- .NET Core 1.x : 2016년 3월이 현재 version이며 .NET Framework 4.6.1에 비해 훨씬 작은 API를 가지고 있습니다.
- .NET Core 2.x : .NET Framework 4.7.1과 동일하게 .NET Standard 2.0을 구현함으로서 동등한 API에 도달하였습니다.
- .NET Core 3.x : .NET Framework 4.8에서 구현하지 않은 .NET Standard 2.1을 구현함으로서 월씬 더 많은 API를 지원하게 되었습니다.
- .NET 5 : 상당히 향상된 성능과 함께 더 .NET Framework 4.8에 비해 더 많은 API를 지원합니다.
- .NET 6 : 지속적인 성능 향상과 API확장을 이루었으며 2022년 5월에 추가된 .NET MAUI를 통해 mobile app을 지원할 수 있게 되었습니다.
- .NET 7 : .NET MAUI로 mobile app지원을 위한 최종 통합 version입니다.
(1) .NET Core 1.0
2016년 6월에 release 되었으며 ASP.NET Core를 사용하는 Linux용 web과 cloud application 그리고 service를 포함한 최신의 cross-platform app개발을 위해 안정적인 API를 제공하는데 초점이 맞춰진 version입니다.
(2) .NET Core 1.1
2016년 11월에 release 되었으며 bug를 수정하고 지원되는 Linux의 배포한 수를 증가되었으며 .NET Standard 1.6를 지원하게 되었습니다. 또한 성능의 증가도 이루어졌는데 특히 web app과 service를 위한 ASP.NET Core에서 큰 성능향상이 이루어졌습니다.
(3) .NET Core 2.0
2017년 8월에 release되었으며 .NET Standard 2.0을 구현하였고 .NET Framework library를 참조하고 더 많은 성능향상을 이루었습니다.
(4) .NET Core 2.1
2018년 5월에 release 되었으며 확장가능한 tooling system(사용 목적에 따라 각각의 도구(기능)를 선택/조합하는 체계)에 중점을 두었습니다. Span<T>, 암호화 및 압축 그리고 예전 windows application을 이식하는데 필요한 20,000가지의 추가적인 API와 함께 Windows Compatibility Pack, Entity Framework Core값 변환, LINQ GroupBy 변환, data seeding, query type과 같은 새로운 type이 추가되었으며 아래 표의 기능을 포함해 전반적인 성능 향상이 이루어졌습니다.
기능 | 내용 |
Spans | - |
Brotli compression | Brotli algorithm을 통한 압축방식 |
EF Core Lazy loading | lazy(지연) loading 사용 |
EF Core Data seeding | - |
(5) .NET Core 2.2
2018년 12월에 release 되었으며 runtime에서의 진단향상, 선택적 계층 compile등에 중점을 두었으며 ASP.NET Core과 Entity Framework에 NetTopologySuite (NTS) library, query tag, 소유 entity collection 등의 type을 사용하는 공간 data지원등 새로운 기능이 추가되었습니다.
(6) .NET Core 3.0
2019년 9월에 release 되었으며 Windows forms, WPF(Windows Presentation Foundation) 그리고 Entity Framework 6.3, side-by-side와 app-local 개발, fast JSON reader, serial port 접근, IoT(Internet of Things) solution을 위한 pinout 접근등을 사용하는 desktop application구축하는데 필요한 지원에 중점을 두었습니다. 또한 아래 기능을 포함해 기본 계층 compile이 추가되었습니다.
기능 | 내용 |
Embedding .NET in-app | - |
Index and Range | - |
System.Text.Json | 고성능 JSON 처리 |
(7) .NET Core 3.1
2019년 12월에 release 되었으며 bug수정과 개선에 초점이 맞춰져 Long Term Support (LTS)가 되었고 이로서 2022년 12월까지 지원이 이루어졌습니다.
(8) .NET 5.0
2020년 10월에 release 되었으며 mobile을 제외한 다양한 .NET platform을 통합하는데 주력하였습니다. platform을 개선하고 아래 표의 기능을 포함해 전반적으로 성능향상이 이루어졌습니다.
기능 | 내용 |
Half type | 반정밀도 부동 소수점 숫자 |
정규 표현식 성능 향상 | 정규 표현식 성능 향상 |
System.Text.Json 향상 | 고성능 JSON 처리 |
EF Core generated SQL | - |
EF Core Filtered Include | Entity filtering |
EF Core의 Humanizer 통합 | - |
(9) .NET 6.0
2021년 10월에 release 되었으며 data관리를 위한 EF Core로 더 많은 기능과 함께 시간과 날짜에 관한 새로운 type이 추가되었습니다. 또한 아래 표의 기능을 포함해 전반적인 성능향상이 이루어졌습니다.
기능 | 내용 |
.NET SDK 상태 확인 | .NET SDK update 확인 |
Apple Silicon 지원 | 배포가능한 console application 생성가능 |
Link trim mode 기본 사용 | app trimming을 통한 app size 감소 |
List<T>에 대한 EnsureCapacity | collection용량 확보를 통한 성능 향상 |
RandomAccess를 사용하는 저수준 file API | - |
EF Core 구성 규칙 | - |
새로운 LINQ method | - |
TryGetNonEnumeratedCount | - |
(10) .NET 7.0
2022년 10월에 release 되었으며 mobile platform을 통합하고 string syntax coloring과 IntelliSense 같은 새로운 기능이 추가되었습니다. tar 형식의 압축을 생성하거나 풀어낼 수 있게 되었고 EF Core를 통한 insert와 upate성능이 향상되었습니다.
기능 | 내용 |
[StringSyntax] attribute | regular expression syntax coloring 활성화 |
[GeneratedRegex] attribute | source generator를 통한 정규 표현식 성능 향상 |
Tar archive support | tar 압축 file |
ExecuteUpdate와 ExecuteDelete | 더욱 효과적인 update및 삭제 |
Order와 OrderDescending | item 자체 정렬 |
(11) .NET 5 이후의 성능 향상
Microsoft는 지난 몇년간 .NET에 대한 상당한 성능향상을 이루어 왔습니다. 이와 관련된 상세한 내용은 아래 글을 통해 확인하실 수 있습니다.
Performance Improvements in .NET 5 - .NET Blog (microsoft.com)
Performance Improvements in .NET 6 - .NET Blog (microsoft.com)
Performance Improvements in .NET 7 - .NET Blog (microsoft.com)
(12) Update를 위한 .NET SDK 확인
.NET 6에서 Microsoft는 설치 SDK와 runtime의 version을 확인하기 위한 명령을 추가했고 이를 통해 update가 필요하다고 확인되는 경우 이를 알려줄 수 있습니다.
명령은 아래와 같으며
dotnet sdk check |
다음과 같은 경우처럼 update가 필요한 항목을 확인할 수 있습니다.
.NET SDKs: Version Status ------------------------ 7.0.304 Up to date. Try out the newest .NET SDK features with .NET 8.0.100-preview.5.23303.2. .NET Runtimes: Name Version Status -------------------------------------------------------------------------- Microsoft.NETCore.App 5.0.17 .NET 5.0 is out of support. Microsoft.NETCore.App 6.0.7 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.7 Patch 6.0.18 is available. Microsoft.NETCore.App 6.0.10 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.10 Patch 6.0.18 is available. Microsoft.NETCore.App 6.0.11 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.11 Patch 6.0.18 is available. Microsoft.NETCore.App 6.0.12 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.12 Patch 6.0.18 is available. Microsoft.NETCore.App 6.0.13 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.13 Patch 6.0.18 is available. Microsoft.AspNetCore.App 6.0.14 Patch 6.0.18 is available. Microsoft.NETCore.App 6.0.15 Patch 6.0.18 is available. Microsoft.WindowsDesktop.App 6.0.15 Patch 6.0.18 is available. Microsoft.AspNetCore.App 6.0.18 Up to date. Microsoft.NETCore.App 6.0.18 Up to date. Microsoft.WindowsDesktop.App 6.0.18 Up to date. Microsoft.NETCore.App 7.0.3 Patch 7.0.7 is available. Microsoft.WindowsDesktop.App 7.0.3 Patch 7.0.7 is available. Microsoft.AspNetCore.App 7.0.7 Up to date. Microsoft.NETCore.App 7.0.7 Up to date. Microsoft.WindowsDesktop.App 7.0.7 Up to date. |
2 .NET Component
.NET은 아래와 같은 요소로 이루어져 있습니다.
- Language compiler : C#이나 F#등으로 된 source code를 assembly로 저장된 IL code로 변환시킵니다. C# 6.0 이후부터는 compiler를 Roslyn이라는 이름으로 open-source화 하였습니다.
- Common Language Runtime(CoreCLR) : Assembly를 load 하여 해당 assembly에 저장된 IL code를 해당 computer의 CPU에 맞는 native code로 compile한뒤 thread와 memory 같은 관리 resource환경 안에서 code를 실행하게 됩니다.
- Base Class Libraries(BCL 또는 CoreFX) : Application을 구축할 때 공통적인 동작을 수행하기 위해 Nuget을 통하여 package 및 배포된 type의 사전 build 된 assembly를 포함할 수 있습니다. 이를 사용하여 LEGO 부품을 조립하듯 필요한 것을 빠르고 안정적으로 구축할 수 있습니다.
(1) Assembly, NuGet package, namespace
Assembly는 type이 filesystem으로 저장된 것입니다. Code를 배포하기 위한 mechanism인데 예를 들어 System.Data.dll assembly는 data를 관리하기 위한 type을 포함하고 있습니다. 다른 assembly의 다른 type을 사용하기 위해서는 해당 assembly가 참조되어 있아야 하며 이때 assembly는 정적(pre-created) 또는 동적(generated at runtime)인 것이 될 수 있습니다. 또한 assembly는 class library인 dll이나 console app과 같은 exe의 단일 file로 compile 될 수도 있습니다.
Assembly는 NuGet package를 통해 내려받기 가능하도록 배포될 수 있으며 이때 다수의 assembly나 다른 resource를 포함할 수 있습니다.
Microsoft NuGet feed는 아래 주소에서 찾을 수 있습니다.
● Namespace
Namespace는 type의 주소입니다. 단순한 이름대신 주소체계를 사용함으로써 type을 식별하기 위한 mechanism으로 활용됩니다.
한 가지 예로 .NET에서 System.Web.Mvc namespace의 interface인 IActionFilter는 System.Web.Http.Filters namespace의 IActionFilter와 구별될 수 있습니다.
● Assembly 의존성
Assembly가 class library로 compile 되고 다른 assembly에서의 사용을 위해 이를 제공하게 된다면 이때 file은 .dll 확장자를 가지게 되며 독립적으로 실행될 수 없습니다.
마찬가지로, assembly가 application으로 compile 되면 이때는 exe확장자를 가지게 되며 독립적으로 실행가능한 상태가 됩니다. (.NET Core 3.0이전에 console app은 dll file로 compile 되었으며 이때는 dotnet run 명령을 통해 실행될 수 있었습니다.)
모든 assembly는 하나 또는 그 이상의 class library를 의존성으로 참조할 수 있습니다. 하지만 소위 순환참조는 불가능하기 때문에 예를 들어 B라는 assembly는 A라는 assembly에서 이미 B assembly를 참조하고 있다면 A assembly를 참조할 수 없습니다. 때문에 compiler는 순환참조를 유발할 수 있는 의존성 참조를 추가하려고 한다면 경고를 표시하게 됩니다. 대게는 오래전에 작성된 code에서 순환참조에 대한 오류가 나오기 쉬우며 만약 정말 순환참조가 필요한 상황이 온다면 이는 interface로 관련 문제를 해결하는 것이 좋습니다.
(2) Microsoft .NET Project SDK
기본적으로 console application은 Microsoft .NET project SDK에 대한 의존성 참조를 가지고 있습니다. 이 platform은 NuGet package에서 거의 대부분의 application에서 필요로 하는, 예를 들어 System.Int32나 System.tring type과 같은 수천 개의 type들을 포함하고 있으며 .NET을 사용할 때는 project file에서 필요로 하는 의존성 assembly와 NuGet package 그리고 platform 등을 참조하게 됩니다.
(3) Assembly에서 namespace와 type
대부분의 일반적인 .NET type은 System.Runtime.dll assembly에 있으며 assembly와 namespace는 항상 1:1로 mapping 되지는 않습니다. 단일 assembly라도 다수의 namespace를 포함할 수 있으며 namespace는 다수의 assembly에서 정의될 수 있습니다. 아래 표는 type을 지원하기 위해 사용되는 일부 assembly와 namespace 간 연관성을 나타내고 있습니다.
Assembly | 사용 Namespace | 제공 Type |
System.Runtime.dll | System, System.Collections, System.Collections.Generic |
Int32, String, IEnumerable<T> |
System.Console.dll | System | Console |
System.Threading.dll | System.Threading | Interlocked, Monitor, Mutex |
System.Xml.XDocument.dll | System.Xml.Linq | XDocument, XElement, XNode |
(4) NuGet package
.NET은 하나가 아닌 몇 개의 package별로 나누어졌고 이를 NuGet이라는 package 관리 기술을 사용해 게시하였습니다. 이렇게 게시된 각각의 package들은 같은 이름의 단일 assembly로 표현됩니다. 예를 들어 System.Collections package는 System.Collections.dll assembly를 포함합니다.
Package는 다음과 같은 장점 및 특징을 가집니다.
- 공개 feed(패키지를 인터넷의 모든 사용자와 공개적으로 공유하는 데 사용)에 쉽게 게시할 수 있습니다.
- 재사용이 가능합니다.
- 자체 일정에 따라 게시될 수 있습니다.
- 다른 package와 독립적으로 test 될 수 있습니다.
- 같은 assembly를 다른 OS와 CPU에 따라 build 한 여러 version을 포함시킴으로써 다른 OS와 CPU를 지원할 수 있습니다.
- 하나의 library에 대해서만 특정한 종속성을 가질 수 있습니다.
- 참조되지 않은 package는 배포의 일부가 아니므로 app은 더 작아질 수 있습니다. 아래 표는 가장 중요한 package와 해당 package에서 중요한 type의 일부를 나타낸 것입니다.
Package | 중요 type |
System.Runtime | Object, String, Int32, Array |
System.Collections | List<T>, Dictionary<TKey, TValue> |
System.Net.Http | HttpClient, HttpResponseMessage |
System.IO.FileSystem | File, Directory |
System.Reflection | Assembly, TypeInfo, MethodInfo |
(5) Framework
Package는 API를 정의하며 framework는 이런 package를 group화 합니다. 어떠한 package도 없는 framework는 어떠한 API도 정의하지 않습니다.
.NET package는 각각 일련의 framework를 지원합니다. 예를 들어 System.IO.FileSystem package version 4.3.0은 아래 framework를 지원합니다.
- .NET Standard, version 1.3 이후
- .NET Framework, version 4.6 이후
- Six Mono와 Xamarin platform (Xamarin.iOS 1.0)
좀 더 상세한 내용은 아래 주소를 참조하시기 바랍니다.
NuGet Gallery | System.IO.FileSystem 4.3.0
(6) Type 사용을 위한 namespace importing
예제를 통해 namespace가 assembly와 type에 어떻게 관련되는지를 알아보도록 하겠습니다. csStudy07 이름의 빈 solution을 생성하고 그 안에 'AssembliesAndNamespaces'이름의 project를 추가합니다.
AssembliesAndNamespaces의 Program.cs에서 기존 구문을 모두 삭제하고 아래와 같이 XDocument type의 instance를 생성하는 문을 추가합니다.
XDocument doc = new();
이 상태에서 Project를 build 하면 다음과 같은 compiler error message가 발생하게 됩니다.
The type or namespace name 'XDocument' could not be found (are you missing a using directive or an assembly reference?) |
XDocument type은 우리가 compiler에게 해당 type의 namespace가 무엇인지를 알려주지 않았기 때문에 인식될 수 없습니다. 설령 project가 type을 포함하고 있는 assembly를 참조하고 있다고 하더라도 type이름에 namespace에 대한 접두사를 붙이거나 해당 namespace를 import 해야 합니다.
XDocument class의 이름에 mouse를 대면 하단에 풍선도움말이 표시되는데 이때 'Show potential pixes'를 click한뒤 menu에 나오는 System.Xml.Linq를 선택하면 자동적으로 해당 Namespace가 file의 상단에 using문을 추가함으로써 import 될 것입니다. 일단 namespace가 import 되고 나면 namespace에 속하는 모든 type은 code에서 namespace전체를 붙여줄 필요 없이 이름만 입력하는 것으로 사용가능하게 됩니다.
Visual Studio를 사용하는 것이라면 위에서 처럼 type을 입력하는 것만으로 인식가능한 namespace를 자동으로 import 해줄 것입니다.(2022 version부터)
(7) C# keyword와 .NET type의 연결
C#에서는 문자열을 다루는 keyword가 2가지가 있는데 하나는 string이고 다른 하나는 String입니다. 이 둘의 차이는 소문자 s와 대문자 S 뿐이며 사실상 용도와 동작에 있어서는 아무런 차이가 없습니다. int와 string과 같은 C# keyword는 class library assembly에 있는 .NET Type의 별칭입니다.
따라서 string keyword를 사용하는 경우에 compiler는 이를 System.String type으로 인식하게 되고 int를 사용하는 경우에는 System.Int32 type으로 인식합니다. 문자열을 다루기 위해 String이라고 하는 것보다는 string으로 사용하는 것이 더 익숙하고 편리한 면이 있으며 Int32보다는 int로 사용하는 것도 같은 이유기 때문에 좀 더 편리한 code의 작성을 위해 별칭으로 따로 만들어둔 것입니다.
Program.cs에서 문자열값을 가진 2개의 변수를 선언하는데 하나는 string으로 다른 하나는 String으로 선언합니다.
string s1 = "Hello";
String s2 = "World";
Console.WriteLine($"{s1} {s2}");
위 예제를 실행하면 두 변수는 정확히 동일하게 취급되어 동일한 처리를 하게 됩니다.
Project의 csproj file에서 전역적으로 import 되는 System namespace를 사용하지 않도록 하는 요소를 아래와 같이 추가합니다.
<ItemGroup>
<Using Remove="System" />
</ItemGroup>
그러면 아래와 같이 compiler 오류가 발생할 것입니다.
이를 통해 String은 System namespace의 String type을 사용한다는 것을 알 수 있습니다.
실제 String과 같은 type대신 C# keyword를 사용하면 위에서와 같이 해당 type의 namespace import를 신경 쓰지 않아도 됩니다.
● C# 별칭과 .NET type의 Mapping
아래 표는 18가지 C# type keyword와 같이 연결된 .NET Type을 나타내고 있습니다.
Keyword | .NET type | Keyword | .NET type |
string | System.String | char | System.Char |
sbyte | System.SByte | byte | System.Byte |
short | System.Int16 | ushort | System.UInt16 |
int | System.Int32 | uint | System.UInt32 |
long | System.Int64 | ulong | System.UInt64 |
nint | System.IntPtr | nuint | System.UIntPtr |
float | System.Single | double | System.Double |
decimal | System.Decimal | bool | System.Boolean |
object | System.Object | dynamic | System.Dynamic.DynamicObject |
다른 .NET Programming 언어 compiler도 같은 방식을 사용할 수 있습니다. 예를 들어 VB.NET의 경우에는 Integer라는 이름의 type을 가지고 있으며 이는 System.Int32의 별칭으로 연결됩니다.
● native-sized integers
C# 9에서는 native-sized integers에 대한 별칭에 해당하는 nint와 nuint를 도입하였습니다. 여기서 저장되는 integer값의 크기는 platform에 따라 다릅니다. 따라서 32bit process에서는 32bit integer로 저장되며 sizeof()에서는 4byte를 반환하고 64bit process에서는 64bit integer로, sizeof()에서는 8byte를 반환합니다. 해당 별칭의 이름이 IntPtr/UIntPtr인데 이를 통해 해당 별칭은 integer값이 저장된 memory의 위치를 나타낸다는 것을 알 수 있으며 실제 저장되는 type은 process에 따라 System.Int32 또는 System.Int64가 됩니다.
Console.WriteLine($"int.MaxValue = {int.MaxValue:N0}");
Console.WriteLine($"nint.MaxValue = {nint.MaxValue:N0}");
● Type의 위치 표시
Visual Studio에서 아래와 같이 string이나 String의 변수를 생성하고
string s;
해당 type을 선택한 뒤 mouse오른쪽 button을 눌러 'Go to Definition'을 선택합니다.
Code editor를 보면 assembly file명은 'System.Runtime.dll'이지만 class는 System namespace에 있음을 알 수 있습니다.
그런데 지난 글에서
[.NET/C#] - [C# 11 과 .NET 7] 2. C#
System.Runtime.dll에 포함된 type은 0 임을 언급한 바 있습니다.
System.Runtime.dll assembly가 포함하고 있는 것은 type-forwarder입니다. 이들은 assembly안에 포함되어 있는 것처럼 표시하지만 다른 곳에 구현되어 있는 특별한 type입니다. 이 경우 string은 고도로 최적화된 code를 사용해 .NET runtime내부에 깊이 구현되어 있습니다.
(8) .NET Standard를 사용한 legacy platform과의 code공유
.NET Standard 이전에는 PCL(Portable Class Libraries)가 있었습니다. PCL에서는 code의 library를 만들고 명시적으로 library에서 지원할 Xamarin, Silverlight, Windows 등의 platform을 지정할 수 있었고, 그렇게 하면 library는 지정된 platform에 의해서 지원되는 API와의 상호작용에 사용될 수 있었습니다.
하지만 Microsoft는 이것이 지속가능하리라고 판단하지 않고 미래 모든 .NET platform에서 지원할 단일 API인 .NET Standard를 생성하게 됩니다. .NET Standard에 이어 .NET Standard 2.0에서는 중요한 최신 .NET platform의 통합을 시도했고 2019년 말에 발표된 .NET Standard 2.1에서는 .NET Core 3.0과 그해 Xamarin에서만이 새로운 기능을 지원했습니다. (앞으로 언급되는 .NET Standard는 .NET Standard 2.0을 의미합니다.)
.NET Standard는 HTML5와 같이 둘다 platform이 지원할 수 있는 표준에 해당합니다. Google의 Chrome browser나 Microsoft의 Edge browser가 HTML5의 표준을 구현하듯 .NET Core, .NET Framework 그리고 Xamarin은 모두 .NET Standard를 구현합니다. 만약 다양한 legacy .NET에 걸쳐 작동하는 type의 library를 만들고자 한다면 .NET Standard를 통해 가장 쉽게 구현할 수 있습니다.
.NET Standard 2.1에서 많은 API가 추가되어 runtime의 변화가 필요했으며 .NET Framework는 가능한 한 변화 없이 유지되어야 할 Microsoft의 legacy platform이므로 .NET Framework 4.8에서는 .NET Standard 2.1을 구현하기 보다는 .NET Standard 2.0에서 남아있습니다. 만약 .NET Framework에서 지원되어야 할 필요가 있다면 비록 최신의 version이 아니고 모든 최근의 언어와 BCL의 새로운 기능을 지원하지 않지만 .NET Standard 2.0상의 class library를 만들 수 있습니다.
어떤 .NET Standard version을 목표로 할지에 대한 선택에서는 지원하고자 하는 platform과 가능한 기능사이에 균형을 맞추는 것이 중요합니다. 하위 version은 더 많은 platform을 지원하지만 더 작은 API를 갖게 됩니다. 상위 version은 적은 platform을 지원하지만 더 많은 API를 가질 수 있습니다. 일반적으로는 필요로 하는 모든 API를 지원하는 선에서 하위 version을 선택할 수 있습니다.
(9) 다른 SDK를 사용하는 class library의 기본 framework
Class library를 만들기 위해 dotnet SDK tool을 사용할 때 어떤 framework가 기본적으로 사용될지 알고 있을 필요가 있습니다. 아래 표는 이를 간략히 나타내고 있습니다.
SDK | 새로운 class library에 사용될 기본 target framework |
.NET Core 3.1 | netstandard2.0 |
.NET 6 | net6.0 |
.NET 7 | net7.0 |
물론 class library가 기본적으로 특정 .NET의 version을 대상으로 한다 하더라도 이것이 기본 template을 사용해 class library project를 생성한 이후에는 바꿀 수 없다는 것을 의미하는 것은 아닙니다.
설정에 의해 target framework를 library참조가 필요한 project에서 지원하는 값으로 바꿀 수 있습니다.
Class library target framework | project에서 사용할 수 있는 대상 |
netstandard2.0 | .NET Framework 4.6.1이후, .NET Core 2.0이후, .NET 5.0이후, Mono 5.4이후, Xamarin.Android 8.0이후, Xamarin.iOS 10.14이후 |
netstandard2.1 | .NET Core 3.0이후, .NET 5.0이후, Mono 6.4이후, Xamarin.Android 10이후, Xamarin.iOS 12.16이후 |
net6.0 | .NET 6.0이후 |
net7.0 | .NET 7.0이후 |
Class library의 대상 framework를 확인하고 필요하다면 더 적절한 것으로 바꿀 수 있습니다. 기본값을 그대로 수용하기보다는 무엇이 적합한지 의식적으로 결정을 내리시면 됩니다.
(10) .NET Standard 2.0 class library를 만들기
.NET Standard 2.0을 사용해 모든 .NET legacy platform과 Windows 및 macOS, Linux OS의 cross-platform에서 사용가능하며 다양한 .NET API에 access 할 수 있는 class library를 만들어볼 것입니다.
csStudy07 solution에 SharedLibrary이름의 Class Library유형의 project를 추가합니다. 이때 .NET Standard 2.0을 target으로 설정해야 합니다.
만약 .NET Standard 2.0 기능만 사용하는 type뿐만 아니라 .NET 7의 새로운 기능을 사용하는 type을 생성해야 한다면 이 두 개의 class library를 분리하여 생성해야 합니다.
2개의 class library를 생성하는 대신 multi-targeting을 지원하는 방법도 존재합니다. 자세한 내용은 아래 link를 참고하시기 바랍니다.
Cross-platform targeting for .NET libraries | Microsoft Learn
(11) .NET SDK 제어
기본적으로 dotnet 명령줄 실행에서는 system에 설치된 가장 최신의 .NET SDK를 사용합니다. 그러나 때로는 이렇게 사용되는 SDK를 변경해야 하는 경우가 있습니다. 그리고 이것은 global.json file을 사용함으로써 가능한데 dotnet명령은 global.json file을 현재 folder와 상위 folder에거 검색합니다.
지금부터 설명하는 절차를 굳이 따라 할 필요는 없지만 그래도 해보고자 한다면 .NET 6.0 SDK를 설치해야 하며 아래 link로부터 가져올 수 있습니다.
Download .NET 6.0 (Linux, macOS, and Windows) (microsoft.com)
csStudy07 project folder하위에 ControlSDK이름의 새로운 folder를 만들고 해당 folder안에서 terminal을 열어 아래 명령을 내려줍니다.
dotnet --list-sdks |
만약 .NET SDK 6.0을 설치했다면 아마도 아래와 같은 결과를 표시할 수 있습니다.
6.0.410 [C:\Program Files\dotnet\sdk] 7.0.304 [C:\Program Files\dotnet\sdk] |
이 상태에서 아래의 명령을 내려 global.json이름의 file을 만들고 아래와 같이 설정하여 설치한 .NET Core 6.0 SDK의 사용을 강제하도록 합니다.
dotnet new globaljson --sdk-version 6.0.410 |
생성된 file을 열어보면 아마도 다음과 같이 저장되어 있을 것입니다.
{
"sdk": {
"version": "6.0.410"
}
}
이제 아래 명령을 통해 class library project의 생성을 시도합니다.
dotnet new classlib |
만약 정상적으로 처리된다면 해당 project가 생성되어 있을 테지만 .NET SDK 6.0이 설치되지 않은 상태라면 다음의 오류가 표시될 것입니다.
The command could not be loaded, possibly because: * You intended to execute a .NET application: The application 'new' does not exist. * You intended to execute a .NET SDK command: A compatible .NET SDK was not found. Requested SDK version: 6.0.410 |
3. 배포를 위한 code 게시하기
만약 소설과 같은 글을 쓴 경우라면 이것을 다른 사람이 읽도록 하기 위해서는 '출판'해야 할 것입니다. 대부분의 개발자는 자신의 project에서 사용하거나 또는 app을 실행하는 일반사용자를 위한 code를 작성합니다. 그렇게 하기 위해서는 class library를 packaging 하거나 실행 가능한 형태의 application으로 게시를 해야 합니다.
.NET application을 배포하고 게시하기 위해서는 아래 3가지 방법을 사용할 수 있습니다.
- Framework-dependent deployment (FDD)
- Framework-dependent executable (FDE)
- Self-contained
만약 application 및 여기에 해당하는 package종속성을 같이 배포하기로 했지만 .NET자체는 제외한다면 대상 computer에 설치된 .NET에 의존해야 합니다. 이것은 server에 web application을 배포하기에 적합한 방식입니다. 왜냐하면 .NET은 물론 다른 web application 이미 server에 존재할 수 있기 때문입니다.
Framework-dependent deployment (FDD)는 dotnet 명령줄 도구를 통해 실행될 수 있는 DLL을 배포하는 경우를 의미하며 Framework-dependent executables (FDE)는 직접적으로 실행될 수 있는 EXE를 배포하는 경우를 의미합니다. 이 둘은 모두 system에 적절한 version의 .NET runtime이 설치되어 있어야 합니다.
때로는 다른 사람에게 USB memory를 통해 application을 전달하고자 할 수 있습니다. 이런 경우 전달받은 사용자의 computer에서 실행될 수 있도록 self-contained deployment를 수행할 수 있습니다. 물론 이렇게 하는 경우 배포 file의 size가 커질 수 있지만 관련된 모든 library를 포함하게 되므로 실행하는 데는 아무런 문제가 없을 것입니다.
(1) 게시를 위한 console app 만들기
csStudy07 solution에서 DotNetEverywhere이름의 Console App project를 생성합니다. 그리고 Program.cs에서 기존 구문을 모두 삭제하고 console app이 어디서든 작동할 수 있음을 말하는 message와 현재 운영체제에 대한 정보를 출력하는 문을 아래와 같이 추가합니다.
Console.WriteLine("I can run everywhere!");
Console.WriteLine($"OS Version is {Environment.OSVersion}.");
if (OperatingSystem.IsMacOS())
{
Console.WriteLine("I am macOS.");
}
else if (OperatingSystem.IsWindowsVersionAtLeast(major: 10, build: 22000))
{
Console.WriteLine("I am Windows 11.");
}
else if (OperatingSystem.IsWindowsVersionAtLeast(major: 10))
{
Console.WriteLine("I am Windows 10.");
}
else
{
Console.WriteLine("I am some other mysterious OS.");
}
Console.WriteLine("Press ENTER to stop me.");
Console.ReadLine();
위 예제를 실행하면 다음과 같은 결과를 표시할 것입니다.(실행되는 환경에 따라 약간씩 다를 수 있습니다.)
Project file(csproj)을 열어 <PropertyGroup> 요소 안에 세 가지 운영체제를 대상으로 하기 위한 runtime 식별자를 아래와 같이 추가합니다.
<Nullable>enable</Nullable>
<RuntimeIdentifiers>
win10-x64;osx-x64;osx.11.0-arm64;linux-x64;linux-arm64
</RuntimeIdentifiers>
추가한 요소는 다음과 같은 의미를 가집니다.
- win10-x64 : Windows 10 또는 Windows Server 2016 64-bit를 의미합니다. 만약 Microsoft Surface Pro X, Surface Pro 9(SQ 3) 또는 Windows Dev Kit 2023으로 배포하고자 한다면 win10-arm64로 설정할 수 있습니다.
- osx-64 : macOS Sierra 10.12이상을 의미합니다. 또한 osx.10.15-x64 (Catalina), osx.13.0-x64 (Ventura on Intel), osx.13.0-arm64 (Ventura on Apple Silicon)등 특정 version을 명시할 수도 있습니다.
- linux-x64 : Ubuntu, CentOS, Debian, Fedora와 같은 Linux의 대부분의 desktop 배포판을 의미합니다. linux-arm을 사용하는 경우에는 Raspbian 또는 Raspberry Pi OS 32-bit를 의미하며 linux-arm64라면 Raspberry Pi running Ubuntu 64-bit를 의미합니다.
특정 runtime을 식별하기 위해 사용가능한 2가지 요소가 있습니다. <RuntimeIdentifier>는 특정한 한 가지만을 지정해야 하는 경우에 사용하며 <RuntimeIdentifiers>는 예제와 같이 여러 가지를 지정해야 하는 경우에 사용합니다. 그런데 만약 잘못된 값을 사용하게 된다면 compiler는 error를 발생시키게 되는데 경우에 따라 단 하나의 잘못된 문자로 error가 발생하는 경우에 무엇이 잘못되었는지 찾기가 어려워질 수 있으므로 주의해야 합니다.
(2) dotnet 명령
.NET SDK를 설치하게 되면 dotnet이라는 이름의 command line interface(CLI)를 같이 설치하게 됩니다.
● 새로운 project 생성
.NET CLI는 template을 사용해 새로운 project를 현재 folder에 생성하는 명령을 포함하고 있습니다.
Terminal을 열고 .NET 7이라면 'dotnet new list'를, .NET 6라면 'dotnet new --list'혹은 'dotnet new -l'명령을 내려줍니다. 그러면 다음과 같이 현재 설치된 template을 표시하게 됩니다.
대부분의 dotnet 명령의 switche는 길고 짧은 version을 각각 사용할 수 있기 때문에 만약 --list라 하면 -l처럼 사용할 수 있습니다. 짧은 것은 typing 하기에 빠를 수 있지만 다른 사람이나 심지어 본인도 잘못 명령을 해설할 수 있으므로 긴 것을 사용하면 의미를 좀 더 명확히 전달할 수 있습니다.
(3) .NET및 실행환경에 대한 정보 파악하기
현재 설치된 .NET SDK및 runtime과 함께 운영체제에 대한 정보를 확인해 보려면 'dotnet --info'명령을 사용하면 됩니다.
(4) Project 관리
.NET CLI는 아래명령을 통해 project를 관리하기 위해 현재 folder에 project에 대한 몇 가지 작업을 수행할 수 있습니다.
- dotnet help : dotnet 명령에 대한 도움말을 표시합니다.
- dotnet new : 새로운 .NET project 또는 file을 생성합니다.
- dotnet tool : .NET 환경을 확장하기 위한 도구를 설치하거나 관리합니다.
- dotnet workload : .NET MAUI와 같은 선택적 workload를 관리합니다.
- donet restore : project에 대한 의존성을 download 합니다.
- dotnet build : .NET Project를 build(compile)합니다.
- dotnet build-server : build에 의해 시작된 server와의 상호작용을 수행합니다.
- dotnet msbuild : MS Build Engine명령을 실행합니다.
- dotnet clean : build로 생성되는 임시 folder를 삭제합니다.
- dotnet test : project에 대한 단위 test를 build 하고 실행합니다.
- dotnet run : project를 build하고 실행합니다.
- dotnet pack : project에 대한 NuGet package를 생성합니다.
- dotnet publish : 의존성 또는 self-contained application과 함께 project를 build하고 게시합니다.
- dotnet add : project에 package 혹은 class library에 대한 참조를 추가합니다.
- dotnet remove : project에 대한 package 혹은 class library 참조를 삭제합니다.
- dotnet list : project에 참조된 package 또는 class library를 listing 합니다.
(5) Self-contained app 게시하기
위에서 만든 cross-platform console app인 DotNetEverywhere를 게시하려면 terminal에서 DotNetEverywhere project가 있는(csproj가 있는) folder로 이동한 뒤 다음과 같은 명령을 내려 project를 build 하고 self-contained release version으로 Windows 10에서의 console application을 게시하도록 합니다.
dotnet publish -c Release -r win10-x64 --self-contained |
이렇게 하면 build engine은 필요한 모든 package를 복구하고 project source code를 assembly DLL로 compile 하여 publish folder를 만들어 저장하게 됩니다.
Determining projects to restore... All projects are up-to-date for restore. DotNetEverywhere -> C:\Users\w10tsp\DotNetEverywhere\bin\Release\net7.0\win10-x64\DotNetEverywhere.dll DotNetEverywhere -> C:\Users\w10tsp\DotNetEverywhere\bin\Release\net7.0\win10-x64\publish\ |
이어서 아래 명령을 추가로 사용해 macOS와 다양한 Linux에서의 release version으로 게시하도록 합니다.
dotnet publish -c Release -r osx-x64 --self-contained dotnet publish -c Release -r osx.11.0-arm64 --self-contained dotnet publish -c Release -r linux-x64 --self-contained dotnet publish -c Release -r linux-arm64 --self-contained |
필요하다면 위와 같은 명령들은 PowerShell과 같은 script 언어를 사용해 자동화하고 cross-platform인 PowerSheel Core를 사용해 모든 OS상에서 script file을 실행할 수 있습니다. 이는 그저 위의 명령들을 저장한 .ps1 확장자 file을 만들고 file을 실행하기만 하면 됩니다. PowerShell에 관해서는 아래 link를 통해 더 자세한 정보를 확인하실 수 있습니다.
cs11dotnet7/docs/powershell at main · markjprice/cs11dotnet7 · GitHub
위 명령을 실행한 뒤 Windows 탐색기를 열어 'DotNetEverywhere\bin\ Release\net7.0' folder로 이동해 보면 다섯 가지 OS에 대한 output folder가 생성되어 있음을 확인하실 수 있습니다.
'win10-x64' folder안에서 publish folder를 선택해 보면 Microsoft.CSharp.dll과 같은 필요한 모든 assembly가 저장되어 있음을 볼 수 있으며 이중 DotNetEverywhere.exe file을 선택하면 대략 file의 크기가 151 KB정도 됨을 보실 수 있습니다.
현재 OS가 Windows라면 해당 file을 double click 하여 program을 실행하고 그 결과를 확인해 볼 수 있습니다.
I can run everywhere! OS Version is Microsoft Windows NT 10.0.19045.0. I am Windows 10. Press ENTER to stop me. |
참고로 publish folder의 전체 크기는 대략 70MB 정도 됩니다. 각 OS별로 만들어진 publish folder를 필요한 OS에 복사하고 실행하면 아마도 정상적으로 실행됨을 알 수 있을 것입니다. 이는 console app이 .NET application에 대한 self-contained 배포판이기 때문입니다.
예제에서는 console app을 사용했지만 ASP.NET Core website나 web service 또는 Windows Forms나 WPF app과 같은 것들도 쉽게 만들 수 있습니다. 물론 이때도 Linux나 macOS가 아닌 Windows computer를 위한 Windows desktop app을 배포할 수도 있습니다.
(6) 단일 file app 게시하기
단일 file로서 게시하고자 한다면 게시할 때 flag를 지정할 수 있습니다. .NET 5에서 단일 file app은 Windows와 macOS에서 사실상 기술적으로 단일 file 배포가 불가능했기 때문에 주로 Linux에 집중되었습니다. 그러나 .NET 6 이후에부터는 Windows에서도 적절한 단일 file app을 만들 수 있게 되었습니다.
만약 app을 실행하고자 하는 computer에 적절한 version의 .NET이 설치되어 있다면 app을 게시할 때 아래와 같이 확장 flag를 사용해 self-contained가 필요하지 않고 단일 file로서 배포(가능하다면)하고자 함을 알려줄 수 있습니다.
dotnet publish -r win10-x64 -c Release --no-self-contained /p:PublishSingleFile=true |
위와 같이 하면 2개의 file이 생성되는데 하나는 exe, 다른 하나는 pdb입니다. exe는 실행가능한 file을 의미하며 pdb는 program debug database로서 debugging에 필요한 정보를 담고 있습니다.
macOS상에서 게시된 application애 대한 exe는 존재하지 않으므로 osx-x64와 같은 명령을 사용해야 하며 생성된 file에는 확장자가 존재하지 않을 것입니다.
만약 pdb file마저도 exe안으로 포함시키고자 한다면 assembly와 함께 배포되도록 해야 하며 그러기 위해서 csproj file에서 <PropertyGroup> 요소 안에 <DebugType> 요소를 추가하고 여기에 embedded를 아래와 같이 설정합니다.
<RuntimeIdentifiers>
win10-x64;osx-x64;osx.11.0-arm64;linux-x64;linux-arm64
</RuntimeIdentifiers>
<DebugType>embedded</DebugType>
그러나 대상 computer에 .NET이 설치되어 있지 않은 상태라면 Linux의 경우 단 2개의 file만을 필요로 하지만 Windows라면 coreclr.dll, clrjit.dll, clrcompression.dll 그리고 mscordaccore.dll과 같은 추가적인 file이 필요합니다.
terminal에서 아래 명령을 통해 Windows 10용 console app의 self-contained release version을 build 합니다.
dotnet publish -c Release -r win10-x64 --self-contained /p:PublishSingleFile=true |
그리고 DotNetEverywhere\bin\Release\net7.0\win10-x64\publish foler에서 DotNetEverywhere.exe file을 선택합니다. 그러면 해당 file의 크기가 거의 65MB 정도로 커지게 되는 것을 알 수 있습니다. 또한 pdb file은 11KB 정도인데 이는 system에 따라 크기가 달라질 수 있습니다.
(7) App trimming을 사용한 app size 줄이기
.NET app을 self-contained app으로 배포하는 데 있어서 한 가지 문제점은 .NET library에서 너무 많은 용량을 차지한다는 것이며 특히 문제는 모든 .NET Library를 brwoser에서 내려받아야 하는 Blazor WebAssembly component가 문제가 될 수 있고 이러한 여러 가지 이유로 size를 줄여줄 필요가 있습니다.
그리고 이러한 문제는 배포 시 사용하지 않는 assembly를 packaging에서 제외하도록 함으로써 해결할 수 있습니다. .NET Core 3.0에서 도입된 app trimming system은 code에서 필요한 assembly를 식별하고 필요하지 않은 것들을 제거할 수 있습니다.
.NET 5에서 trimming은 개별 type을 제거하고 심지어 assembly안에서 사용되지 않는 method등의 member를 제거할 수도 있습니다. template에 의해 생성된 기본 console app의 경우 trimming된 System.Console.dll assembly는 대략 60KB에서 30KB로 줄어드는데 .NET 5에서 당시 이 기능은 실험적인 기능이라 기본적으로 사용되지 않도록 disable되어 있습니다.
.NET 6에서 Microsoft는 자신들의 library에 주석을 추가하여 어떤 식으로 안전하게 trimming 할 수 있는지를 나타내도록 하였으며 이에 따라 type과 member의 trimming이 기본적으로 사용되도록 적용되었고 이를 link trim mode라고 합니다.
문제는 trimming이 사용되지 않는 assembly, type, member 등을 얼마나 잘 식별하느냐 하는 것입니다. 만약 code가 동적인 것, 예를 들어 reflection과 같은 것이라면 정확하게 작동하지 않을 수 있고 이것 때문에 Microsoft는 수동적인 조정이 가능하도록 하였습니다.
● Assembly 수준 trimming 사용
Assembly 수준의 trimming을 사용하는 데는 2가지 방법이 있습니다. 첫 번째는 csproj(project file)에 아래와 같은 요소를 추가하는 것입니다.
<PublishTrimmed>true</PublishTrimmed>
두 번째는 게시할 때 아래와 같이 flag를 사용하는 것입니다.
dotnet publish -c Release -r win10-x64 --self-contained -p:PublishTrimmed=True |
● Type 및 member수준의 trimming 사용
여기에도 2가지 방법이 있는데 첫 번째는 위의 예와 마찬가지로 아래의 요소를 추가하는 것이며
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>Link</TrimMode>
두 번째 방법으로 flag를 사용하는 것입니다.
dotnet publish ... -p:PublishTrimmed=True -p:TrimMode=Link |
.NET 6에서 link trim mode는 기본적으로 사용할 수 있으므로 assembly수준의 trimming을 의미하는 copyuse와 같은 다른 trim mode를 사용하고자 하는 경우일 때만 swich를 지정하면 됩니다.
4. .NET assembly decompile
C#을 학습하기 위한 가장 좋은 방법 중에 하나는 수준 높은 다른 누군가가 만들어 놓은 code를 보는 것입니다.
물론 학습목적이 아닌 자신의 library나 application에 사용할 목적으로 code를 복사하기 위한 decompile을 시도해 볼 수도 있을 것입니다. 어쨌건 decompile은 그들의 지적재산권을 침해하는 것으로 귀결될 수 있으므로 주의해야 합니다.
(1) Visual Studio 2022의 ILSpy extension을 사용한 decompile
.NET assembly의 decompile은 ILSpy와 같은 도구를 통해 가능합니다. Visual Studio에서 Extensions -> Manage Extensions menu를 선택합니다.
왼쪽 category에서 online을 선택하고 search에 'ILSpy'를 입력하고 ILSpy 2022 선택한 뒤 download button을 눌러줍니다.
Download가 완료되면 Visual Studio를 닫아 내려받은 ILSpy 2022의 설치를 완료합니다.
Visual Studio를 재시작하고 csStudy07 project를 열어줍니다. 그리고 solution explorer에서 DotNetEverywhere project를 mouse오른쪽 click한뒤 'Open output in ILSpy'를 선택합니다.
잠시가다리면 ILSpy가 열리게 되는데 decompile 할 언어설정이 C#으로 되어 있는지 확인합니다. 왼쪽 화면에는 'Assemblies navigation'화면이 보이는데 여기에서 DotNetEverywhere를 확장합니다. 이어서 '{ }'와 Program을 차례로 확장한 뒤 '<Main>$(string[]) : void'부분을 click 하여 compiler가 생성한 Program class와 보간문자열이 어떻게 작동하는지를 나타내는 <Main>$ method를 표시합니다.
계속해서 ILSpy에서 File -> Open menu를 선택한 뒤 'DotNetEverywhere/bin/Release/net7.0/linux-x64' foler에서 System.Linq.dll file을 열어봅니다. 그리고 Assemblies tree에서 'System.Linq (7.0.0.0, .NETCoreApp, v7.0)'과 System.Linq namespace, Enumerable class를 차례로 확장한 뒤 'Count<TSource>(this IEnumerable<TSource>) : int'를 선택합니다.
Count method를 보면 몇 가지 배워볼 만한 점을 발견할 수 있습니다.
- source 매개변수를 확인하고 null이라면 ArgumentNullException 예외를 발생시키도록 합니다.
- source는 좀 더 효휼적으로 읽어 들일 수 있는 자체의 Count속성으로 구현할 수 있는 Interface를 확인합니다.
- source의 모든 item을 열거하고 counter를 증가시키는 마지막 method는 구현 효율성이 가장 낮습니다.
Count method의 source code를 확인하고
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (source is ICollection<TSource> collection)
{
return collection.Count;
}
if (source is IIListProvider<TSource> iIListProvider)
{
return iIListProvider.GetCount(onlyIfCheap: false);
}
if (source is ICollection collection2)
{
return collection2.Count;
}
int num = 0;
using IEnumerator<TSource> enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
num = checked(num + 1);
}
return num;
}
그다음 Intermediate Language (IL)에서 생성한 것과 동일한 code를 비교해 보기 위해 toolbar에서 C#에 대한 dropdownlist를 ILSpy로 변경합니다.
C# compiler가 source code를 IL code로 변환시킨다는 것만 알고 있으면 될 뿐 사실 IL code가 딱히 유용하지는 않습니다. 위의 source code만 가지고도 Count method가 인수에 대해 어떻게 null을 확인하는지 등을 알 수 있으므로 훨씬 유용하게 활용될 수 있습니다.
아래 link에서는 Visual Studio에서 ILSpy를 어떻게 사용할 수 있는지를 확인할 수 있습니다.
cs11dotnet7/docs/code-editors/vscode.md at main · markjprice/cs11dotnet7 · GitHub
(2) Visual Studio 2022를 통한 source link 보기
Decompile대신 Visual Studio에는 source link를 사용해 본래 source code를 볼 수 있는 기능을 가지고 있습니다.
csStudy07 solution에서 'SourceLinks'라는 Console app유형의 새로운 project를 생성하고 Program.cs에서 string변수를 선언하고 해당 변수가 가진 문자열의 수를 다른 변수에 할당하고 확인하는 문을 아래와 같이 추가합니다.
string name = "cliel";
int length = name.Count();
Console.WriteLine($"{name} has {length} characters.");
이때 Count method에서 mouse오른쪽 button을 click 하여 'Go To Implementation'을 선택합니다. 그러면 Count.cs이름의 source code file이 표시될 텐데 여기서 우리는 count와 관련된 다섯 개의 method가 Enumerable이라는 partial class에 구현되어 있음을 알 수 있습니다.
이러한 source links를 통해서도 위 예제와 같이 관리의 용이성을 위해 어떻게 partial class를 통해서 class를 나누는지와 같은 상황별 예시를 확인할 수 있기 때문에 decompile 하는 것만큼 많은 것을 배울 수 있습니다. ILSpy를 사용하는 경우에도 물론 Enumerable class에 대한 수천 개의 method를 모두 볼 수 있습니다.
아래 link를 통해서는 source links에 관한 더 많은 것과 얼마나 많은 NuGet package가 이를 지원할 수 있는지를 확인해 볼 수 있습니다.
Source Link and .NET libraries | Microsoft Learn
(3) 기술적으로 decompile을 막을 수 있는 방법은 없습니다.
누군가는 compile 된 code에서 decompile을 막을 수는 없을까 하는 의문을 제기할 수도 있을 것입니다. 이 질문에 정확하게 답하자면 '그렇게 할 수 없다.'라고 할 수 있습니다. 대신 Dotfuscator와 같은 도구를 사용해 code를 읽기 어렵게 만들 수는 있지만 decompile자체는 막을 수 없습니다.
모든 compile 된 application은 동작의 대상이 되는 platform, OS 그리고 hardware에 대한 명령을 포함하고 있습니다. 이들 명령은 기능적으로 본래 source code와 일치하지만 사람에게만 읽기 어렵게 만들수 있을 뿐입니다.
Source code를 보호하고자 한다면 여기에 debugger을 연결하고 단계적으로 code를 통과하는 것도 막을 수 있습니다. compile된 application이 pdb file을 가지고 있다면 debugger를 연결하고 구문을 한 줄씩 실행시킬 수도 있습니다. pdb file이 없어도 여전히 debugger를 연결하고 code가 어떻게 작동하는지를 알 수 있습니다.
사실 decompile에 관한 이러한 내용은 C#, Visual Basic, F#과 같은 .NET언어 뿐만 아니라 C, C++, Delphi 및 assembly 언어(debugging에 연결하거나 disassembled 또는 decompile 될 수 있는)에도 해당하는 사실입니다. 아래 표는 해당 목적을 위해 사용하는 일부 도구를 나타내고 있습니다.
목적 | 도구 | 설명 |
Debugger | SoftICE | 운영체제(일반적으로 VM) 아래에서 실행됩니다. |
Debugger | WinDbg | 다른 debugger보다 Windows data 구조에 관한 더 많은 것을 이해하는데 유용한 도구입니다. |
Disassembler | IDA Pro | malware 분석등에 사용됩니다. |
Decompiler | HexRays | IDA Pro의 plugin이며 C app을 decompile합니다. |
Decompiler | DeDe | Delphi app을 decompile합니다. |
Decompiler | dotPeek | JetBrains사에서 만든 것으로 .NET decompiler입니다. |
다른 누군가의 software를 Debugging, disassembling, decompiling 하는 행위는 license동의에 반하며 불법일 수 있습니다. 기술적인 방법으로 지적재산권을 보호하는 대신 법이 유일단 수단이 될 수 있습니다.
5. .NET Framework 에서 .NET으로의 이식
기존 .NET Framework 개발자라면 아마도 기존의 application을 .NET으로 전환하고자 할 수 있을 것입니다. 하지만 정말 .NET으로의 전환이 가장 좋은 방법인지는 심도 있게 고려해봐야 합니다. 단지 .NET으로의 이식만이 최상의 방법이 되지는 않습니다.
예를 들어 .NET Framework 4.8에서 동작하는 사용자가 그리 많지 않은 website project가 있습니다. 그러면 해당 website는 최소한의 hardware상에서 방문자 traffic은 충분히 제어되고 작 잘 동할 수 있을 텐데 이것을 .NET platform으로 전환하는데 잠재적으로 수개월을 소요하는 것은 시간낭비가 될 수 있습니다. 하지만 website가 현재 다수의 고비용 Windows Server가 필요하다면 더 비용이 적은 Linux server로 migration 할 수 있습니다.
(1) 전환 가능한 것
.NET은 Windows, macOS, Linux에 대한 아래 type의 application을 지원하고 있으며 때문에 전환이 가능합니다.
- Razor Pages와 MVC를 포함한 ASP.NET Core web site
- Web API, Minimal API, OData를 포함하는 ASP.NET Core web service(REST/HTTP)
- gRPC, GraphQL, SignalR을 포함하는 ASP.NET Core-hoste
- 명령행 interface인 Console App포함
.NET은 Windows에 대한 아래 Type의 application을 지원하고 있으며 운영에 적합이 가능합니다.
- Windows Forms applications
- Windows Presentation Foundation (WPF) applications
.NET은 cross-platform desktop과 mobile device에 대한 아래 type의 application을 지원하고 있으며 전환이 가능합니다.
- Mobile iOS와 Android를 위한 Xamarin app
- Desktop Windows와 macOS 또는 mobile iOS와 Android를 위한 .NET MAUI
.NET은 아래 legacy Microsoft project에 대한 지원을 하지 않으며 전환이 불가능합니다.
- ASP.NET Web Forms website. (ASP.NET Core Razor Page 또는 Blazor로의 재구현이 필요합니다.)
- Windows Communication Foundation (WCF ) service (단, 요구사항에 따라 사용가능한 open-source project인 CoreWCF가 존재합니다.). (ASP.NET Core gRPC service로의 재구현이 필요합니다.)
- Silverligth application. (Blazor 또는 .NET MAUI로의 재구현이 필요합니다.)
Silverlight와 ASP.NET Web Forms application은 .NET으로의 전환이 불가능하며 기존의 Windows Forms와 WPF application을 WIndows전용의 .NET으로 전환하면 이에 따른 새로운 API와 고성능에 대한 이점을 가져갈 수 있습니다.
Legacy ASP.NET MVC web application과 ASP.NET Web API web service역시 .NET으로의 전환이 가능하며 Windows, Linux 또는 macOS에서 host가 가능합니다.
(2) 전환해야 하는 것
전환이 가능하다면 그렇게 하는 것이 맞을까? 그렇게 한다면 어떤 이익을 얻을 수 있을까? 전환함으로써 얻을 수 있는 장점으로는 대략 다음과 같이 정리할 수 있습니다.
- Website와 webservice에 대한 Linux, Docker 또는 Kubernetes로의 배포 : 해당 OS들은 website와 web service platform으로서 매우 가볍고 특히 Windows Server와 비교해서는 훨씬 비용 효과적이라고 할 수 있습니다.
- IIS와 System.Web.dll에 대한 의존성 제거 : Windows Server로의 배포를 계속한다고 하더라도 ASP.NET Core는 Kestrel web server(또는 다른 것도 마찬가지)에서 가벼우면서도 고성능의 hosting이 가능합니다.
- 명령줄 도구 : 개발자와 관리자가 자신들의 작업을 자동화하기 위해 사용하는 도구는 종종 console application으로 build 되는데 cross-platform에서 동작가능한 단일 도구는 그 자체로 매우 유용하게 사용될 수 있습니다.
(3) .NET Framework와 .NET의 차이
이들 사이에는 대략 3가지 핵심적인 차이가 존재합니다.
.NET | .NET Framework |
NuGet package로 배포되어 있으며 각 application은 필요한 .NET version의 자체 app local 복사를 통해 배포될 수 있습니다. | System전반에 배포되어 있으며 assembly들은 공유됩니다.(GAC-Global Assembly Cache) |
Component 계층으로 작게 나누어져 있으므로 최소한의 배포가 가능합니다. | 단일적이며 획일적인 배포만이 가능합니다. |
ASP.NET Web Forms와 같은 고전 기술과 AppDomains, .NET Remoting, binary serialization과 같은 비 cross-platform 기능들이 제거되었습니다. | .NET에서의 ASP.NET Core MVC와 같은 비슷한 기능뿐만 아니라 ASP.NET Web Forms와 같은 고전적인 기술까지도 남아 있습니다. |
(4) .NET Portability Analyzer
Microsoft는 기존의 application에 대한 이식성의 보고서를 생성하는 유용한 도구를 지원하고 있습니다. 아래 link에서 해당 도구의 시연장면을 볼 수 있습니다.
A Brief Look at the .NET Portability Analyzer | Microsoft Learn
(5) .NET Upgrade Assistant
Legacy project를 .NET으로 upgrade 하기 위한 가장 최신의 도구로는 .NET Upgrade Assistant가 있습니다. 현재 다음과 같은 .NET Framework project type을 지원하고 있으며 향후에는 더 추가될 것으로 예상됩니다.
- ASP.NET MVC
- Windows Forms
- WPF
- Console Application
- Class Library
전역 dotnet 도구를 통해 .NET Upgrade Assistant를 설치하려면 아래의 명령을 내려줍니다.
dotnet tool install -g upgrade-assistant |
아래 link를 통해 .NET Upgrade Assistant에 대한 더 자세한 내용과 사용법을 확인할 수 있습니다.
Overview of the .NET Upgrade Assistant - .NET Core | Microsoft Learn
(6) 비 .NET Standard library 사용
NuGet package는 .NET Standard 또는 .NET 7과 같은 version을 통해 compile 되지 않은 것이라 하더라도 대부분 .NET에서 사용될 수 있습니다. 만약 공식적으로 .NET Standard를 지원하지 않는 package라고 nuget.org web page에 표시된 package가 있어도 단정적으로 사용 못할 것이라 포기할 필요는 없습니다. 일단 시도해 보면서 되는지 안되는지의 여부를 판단해도 됩니다.
예를 들어 아래와 같이 문서화된 것으로, Dialect Software LLC에서 제작된 행렬처리를 위한 사용자 지정 collection package는 .NET Core나 .NET 7이 존재하기 훨씬 이전인 2013년도가 마지막 update로 확인됩니다.
NuGet Gallery | DialectSoftware.Collections.Matrix 1.0.0
따라서 해당 package는 .NET Framework로 build 된 것이며 이와 같은 package가 .NET Standard에서만 가능한 API만 사용하는 경우 .NET project에서도 사용할 수 있습니다.
AssembliesAndNamespaces project에서 아래와 같이 Dialect Software의 package를 참조 추가합니다.
<ItemGroup>
<PackageReference Include="DialectSoftware.Collections.Matrix" Version="1.0.0" />
</ItemGroup>
그리고 Program.cs에서 DialectSoftware.Collections과 DialectSoftware.Collections.Generics를 import 하고 아래와 같이 Axis와 Matrix<T>의 instance를 생성하고 값을 채워 넣은 다음 해당 값을 표시하는 문을 추가합니다.
Axis x = new Axis("x", 0, 100, 1);
Axis y = new Axis("y", 0, 10, 1);
Matrix<long> matrix = new Matrix<long>(new[] { x, y });
int i = 0;
for (; i < matrix.Axes[0].Points.Length; i++)
{
matrix.Axes[0].Points[i].Label = "x" + i.ToString();
}
i = 0;
for (; i < matrix.Axes[1].Points.Length; i++)
{
matrix.Axes[1].Points[i].Label = "y" + i.ToString();
}
foreach (long[] c in matrix)
{
matrix[c] = c[0] + c[1];
}
foreach (long[] c in matrix)
{
Console.WriteLine("{0},{1} ({2},{3}) = {4}", matrix.Axes[0].Points[c[0]].Label, matrix.Axes[1].Points[c[1]].Label, c[0], c[1], matrix[c]);
}
Project를 실행하면 다음과 같은 결과를 표시할 것입니다.
6. Preview 기능 사용하기
Microsoft는 주기적으로 runtime, 언어 compiler, API library등 .NET의 많은 부분에 전역적으로 영향을 미치는 새로운 기능을 제공합니다. 그런데 실질적으로 Microsoft가 기능에 필요한 대부분의 작업을 완료했다고 하더라도 .NET release에 대한 주기 때까지는 배포가 이루어지지 않을 것입니다. 문제는 이렇게 되면 실전에서 적절한 test를 하기에는 너무 늦다는 것입니다.
따라서 .NET 6부터 Microsoft는 GA(general availability) release에서는 preview 기능을 포함하게 되었습니다. 개발자는 이들 미리 보기 기능을 선택하고 Microsoft에게 feedback을 제공할 수 있습니다. 최신 GA release는 모두가 사용할 수 있습니다.
Preview 기능에 관한 내용임에 주의해야 합니다. .NET 혹은 Visual Studio에 대한 preview version과는 다릅니다. Microsoft는 Visual Studio와 .NET에 대한 preview version을 개발자들로부터 feedback을 얻고자 개발 중에 release 하며 그런 뒤 최종 GA를 release 합니다. 이때 GA가 모두가 사용가능한 기능이 되는 것입니다. GA이전에는 새로운 기능을 경험해 볼 수 있는 방법은 preview version을 설치하는 것입니다. Preview 기능은 GA release와 함께 설치되며 선택적으로 사용가능해야 하므로 다르다고 할 수 있습니다.
예를 들어 Microsoft는 .NET SDK 6.0.200를 2022년 02월에 release 했는데 이것은 C# 11 compiler를 preview 기능으로서 포함시켰습니다. 이는 .NET 6 개발자가 선택적으로 언어의 version을 preview로 설정할 수 있었음을 의미하며 이것으로 raw string literal과 required keyword 같은 C# 11 기능을 사용해 볼 수 있었습니다.
Preivew 기능은 production code에서는 지원하지 않으며 최종 release전에 대폭적인 변화가 있을 가능성이 있습니다. 따라서 오로지 test목적으로만 사용해야 합니다.
(1) Preview 기능이 필요한 경우
[RequiresPreviewFeatures] attribute는 사용하는 assembly, type 또는 member를 나타내는 데 사용되며 Code analyzer는 해당 assembly를 scan 하고 필요하다면 경고를 생성하게 됩니다. 만약 code에서 어떠한 preview 기능도 사용하지 않는다면 어떠한 경고 message도 보지 않게 될 테지만 그렇지 않다면 preview 기능을 사용한다는 경고를 해당 code를 사용하는 이에게 표시하게 됩니다.
(2) Preview 기능 사용
csproj(project file)에서 아래와 같이 preview 기능과 preview 언어 기능을 사용하기 위한 요소를 추가합니다.
<Nullable>enable</Nullable>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<LangVersion>preview</LangVersion>