프로젝트에서 참조하는 어셈블리 선택

어셈블리는 빌드 중에 두 가지 방식으로 사용됩니다. 첫 번째는 컴파일입니다. 이에 따라 패키지 소비자의 코드가 어셈블리에 있는 API를 기준으로 컴파일되고 Intellisense가 제안을 제공할 수 있습니다. 두 번째는 런타임입니다. 어셈블리가 bin 디렉터리에 복사되어 프로그램 실행 중에 사용됩니다. 컴파일 시간에 패키지 소비자가 패키지 작성자의 자체 어셈블리(또는 자체 어셈블리의 하위 집합)만 사용할 수 있도록 하면서도 런타임을 위해 모든 종속성을 제공해야 하는 경우가 있습니다. 이 문서에서는 이렇게 하기 위한 방법을 살펴봅니다.

Microsoft가 권장하는 방법은 어셈블리 하나당 패키지 하나를 갖고, 다른 어셈블리에 대한 패키지 종속성을 갖도록 하는 것입니다. NuGet은 프로젝트를 복원할 때 자산 선택을 수행하며, 여러 자산 클래스를 포함하고, 제외하고, 프라이빗으로 만드는 것을 지원합니다. 패키지의 종속성이 패키지를 사용하는 누구에게도 컴파일 시간 자산이 되지 않도록 하려면 compile 자산을 프라이빗으로 만들 수 있습니다. 이렇게 하면 생성된 패키지에서 compile이 종속성에서 제외됩니다. 기본 프라이빗 자산은 지정되지 않은 경우 contentfiles;build;analyzers입니다. 따라서 PackageReference 또는 ProjectReference에서 PrivateAssets="compile;contentfiles;build;analyzers"를 사용해야 합니다.

<ItemGroup>
  <ProjectReference Include="..\OtherProject\OtherProject.csproj" PrivateAssets="compile;contentfiles;build;analyzers" />
  <PackageReference Include="SomePackage" Version="1.2.3" PrivateAssets="compile;contentfiles;build;analyzers" />
</ItemGroup>

NuGet이 자동으로 패키지를 생성하도록 하는 대신 사용자 지정 nuspec 파일에서 패키지를 만드는 경우에는 nuspecexclude XML 특성을 사용해야 합니다.

<dependencies>
  <group targetFramework=".NETFramework4.8">
    <dependency id="OtherProject" version="3.2.1" exclude="Compile,Build,Analyzers" />
    <dependency id="SomePackage" version="1.2.3" exclude="Compile,Build,Analyzers" />
  </group>
</dependencies>

이 솔루션이 권장되는 데는 세 가지 이유가 있습니다.

첫째, 유용한 어셈블리는 새로운 어셈블리/패키지에 의해 참조되는 경우가 많습니다. 지금 당장은 유틸리티 어셈블리를 하나의 패키지만 사용하기 때문에 두 어셈블리를 하나의 패키지에 포함하고 싶을 수 있겠으나, 추후 또 다른 패키지가 “프라이빗” 유틸리티 어셈블리를 사용해야 하는 경우 유틸리티 어셈블리를 새로운 패키지로 이동하고 기존 패키지를 업데이트하여 종속성을 선언하거나 유틸리티 패키지를 기존 패키지와 새로운 패키지에 모두 포함해야 합니다. 어셈블리를 두 개의 패키지에 포함했고 프로젝트에서 두 패키지를 모두 참조하는 경우, 두 패키지에 서로 다른 버전의 유틸리티 어셈블리가 포함된다면 NuGet이 버전 관리를 지원할 수 없게 됩니다.

둘째, 패키지를 사용하는 다른 개발자가 종속성에 포함된 API도 사용하고자 하는 경우가 있을 수 있습니다. Microsoft.ServiceHub.Client version 3.0.3078 패키지를 예로 들어 보겠습니다. 패키지를 다운로드하여 nuspec 파일을 살펴보면 Microsoft.VisualStudio.를 시작으로 두 개의 패키지가 종속성으로 나열되어 있는 것을 볼 수 있습니다. 즉, 이 두 개의 패키지는 런타임에 필요하지만, 해당 패키지의 컴파일 자산은 제외되어 있습니다. 따라서 Microsoft.ServiceHub.Client를 사용하는 프로젝트는 프로젝트가 명시적으로 해당 패키지를 설치하지 않는 한 프로젝트를 빌드할 때나 IntelliSense에서 Visual Studio API를 사용할 수 없습니다. 이것은 제외 자산을 갖는 패키지 종속성의 이점이기도 합니다. 패키지를 사용하는 프로젝트가 종속성도 사용하려면 API를 사용하기 위해 패키지에 대한 참조를 추가하면 됩니다.

마지막으로, 패키지에 여러 개의 어셈블리가 포함된 경우 NuGet의 패키지 어셈블리 선택이 둘 이상의 대상 프레임워크를 지원한다고 오해하는 패키지 작성자들이 있습니다. 기본 어셈블리가 유틸리티 어셈블리에 대해 여러 개의 대상 프레임워크를 지원하는 경우, 어셈블리를 어느 lib/ 디렉터리에 포함해야 할지 명확히 파악하기가 어려울 수 있습니다. 각 패키지를 어셈블리 이름으로 구분하면 각 어셈블리가 어느 lib/ 폴더에 들어가야 할지 보다 직관적으로 알 수 있습니다. 단, 이는 Package1.net48 패키지와 Package1.net6.0 패키지를 갖는 것을 의미하지 않으며, Package1에는 lib/net48/Package1.dlllib/net6.0/Package6.0을, Package2에는 lib/netstandard2.0/Package2.dlllib/net5.0/Package2.dll을 갖는 것을 의미합니다. Nuget은 프로젝트를 복원할 때 두 패키지에 대해 독립적으로 자산 선택을 수행합니다.

종속성 포함/제외 자산은 PackageReference를 사용하는 프로젝트에 의해서만 사용됩니다. packages.config를 사용하여 패키지를 설치하는 모든 프로젝트는 종속성을 설치하고 API를 사용할 수 있게 됩니다. packages.config는 Visual Studio의 이전 .NET Framework 프로젝트 템플릿에서만 지원됩니다. .NET Framework를 대상으로 하는 프로젝트를 포함하여 SDK 스타일 프로젝트는 packages.config를 지원하지 않으며, 따라서 종속성 포함/제외 자산을 지원하지 않습니다.

PackageReferencepackages.config는 서로 다른 기능을 갖습니다. PackageReference를 사용하는 패키지 소비자와 packages.config를 사용하는 소비자, 또는 둘 다른 사용하는 소비자 중 누구를 지원하려는지에 따라 패키지 작성 방법이 달라집니다.

NuGet의 MSBuild Pack은 패키지에 자동으로 프로젝트 참조를 포함하지 않으며, 참조된 프로젝트를 패키지 종속성으로 나열하기만 합니다. 이 GitHub 이슈에서는 커뮤니티 구성원들이 (패키지에 내용 포함 문서에서 설명하는 방법에 따라) PackagePath MSBuild 항목 메타데이터를 사용하여 패키지의 원하는 곳에 파일을 배치하고 프로젝트 참조가 패키지 종속성이 되지 않도록 SuppressDependenciesWhenPacking을 사용하여 이 결과를 달성하는 방법을 공유했습니다. NuGet 공식 팩에 대한 대안으로 사용할 수 있는 커뮤니티 개발 도구도 있는데, 해당 도구는 이 기능을 지원합니다.

PackageReference 지원

패키지 소비자가 PackageReference를 사용하는 경우 NuGet은 앞에서 설명한 것처럼 컴파일 자산과 런타임 자산을 독립적으로 선택합니다.

컴파일 자산은 ref/<tfm>/*.dll을 선호하지만(예: ref/net6.0/*.dll), 존재하지 않는 경우 lib/<tfm>/*.dll을 사용합니다(예: lib/net6.0/*.dll).

런타임 자산은 runtimes/<rid>/lib/<tfm>/*.dll을 선호하지만(예: (runtimes/win11-x64/lib/net6.0/*.dll)), 존재하지 않는 경우 lib/<tfm>/*.dll을 사용합니다.

ref\<tfm>\의 어셈블리는 런타임에 사용되지 않으므로 패키지 크기를 줄이기 위한 메타데이터 전용 어셈블리일 수 있습니다.

packages.config 지원

packages.config를 사용하여 NuGet 패키지를 관리하는 프로젝트는 일반적으로 lib\<tfm>\ 디렉터리의 모든 어셈블리에 참조를 추가합니다. ref\ 디렉터리는 PackageReference를 지원하기 위해 추가되었으므로 packages.config 사용 시 고려되지 않습니다. packages.config를 사용하는 프로젝트에 대해 참조되는 어셈블리를 명시적으로 설정하려면 패키지에서 nuspec 파일의 <references> 요소를 사용해야 합니다. 예시:

<references>
    <group targetFramework="net45">
        <reference file="MyLibrary.dll" />
    </group>
</references>

MSBuild 팩 대상은 <references> 요소를 지원하지 않습니다. MSBuild 팩을 사용할 때는 .nuspec 파일을 사용하여 압축 문서를 참조하세요.

참고 항목

packages.config 프로젝트는 ResolveAssemblyReference라는 프로세스를 사용하여 어셈블리를 bin\<configuration>\ 출력 디렉터리에 복사합니다. 프로젝트의 어셈블리가 복사된 다음, 빌드 시스템이 참조된 어셈블리에 대한 어셈블리 매니페스트를 살펴본 후, 해당 어셈블리를 복사하고 모든 어셈블리에 대해 재귀적으로 반복합니다. 즉, 리플렉션(Assembly.Load, MEF 또는 그 밖의 종속성 삽입 프레임워크)에 의해서만 로드된 어셈블리가 있다면 bin\<tfm>\에 있더라도 프로젝트의 bin\<configuration>\ 출력 디렉터리에 복사되지 않을 수 있습니다. 또한 P/Invoke를 사용하여 호출된 네이티브 코드에 대해서는 작동하지 않으며 .NET 어셈블리에 대해서만 작동합니다.

PackageReferencepackages.config 둘 다 지원

Important

패키지가 ref\<tfm>\에 nuspec <references> 요소를 포함하고 어셈블리는 포함하지 않는 경우, NuGet은 nuspec <references> 요소에 나열된 어셈블리를 컴파일 자산이자 런타임 자산으로 보급합니다. 이는 참조된 어셈블리가 lib\<tfm>\ 디렉터리에 다른 어셈블리를 로드해야 하는 경우 런타임 예외가 있음을 의미합니다. 따라서 packages.config 지원을 위해 nuspec <references>를 사용하고 PackageReference 지원을 위해 ref/ 폴더에서 어셈블리를 복제하는 것이 중요합니다. runtimes/ 패키지 폴더는 사용할 필요가 없습니다. 위 섹션에는 모든 요소를 보여 주기 위해 추가되었습니다.

예시

내 패키지에는 .NET Framework 4.7.2를 대상으로 하는 세 개의 어셈블리 MyLib.dll, MyHelpers.dllMyUtilities.dll이 포함됩니다. MyUtilities.dll에는 다른 두 어셈블리에서만 사용할 수 있는 클래스가 포함되어 있으므로 IntelliSense에서 또는 패키지를 사용하여 프로젝트를 컴파일할 때 해당 클래스를 사용할 수 없도록 합니다. 내 nuspec 파일에 다음 XML 요소가 포함되어 있어야 합니다.

<references>
    <group targetFramework="net472">
        <reference file="MyLib.dll" />
        <reference file="MyHelpers.dll" />
    </group>
</references>

패키지 콘텐츠가 다음과 같은지 확인해야 합니다.

lib\net472\MyLib.dll
lib\net472\MyHelpers.dll
lib\net472\MyUtilities.dll
ref\net472\MyLib.dll
ref\net472\MyHelpers.dll