Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bash committed Mar 20, 2023
0 parents commit 0d35512
Show file tree
Hide file tree
Showing 18 changed files with 787 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.{props,sln,targets}]
indent_style = tab

[*.{yml,yaml}]
indent_size = 2

3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.sln text eol=crlf
*.cs diff=csharp
35 changes: 35 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Build

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
- name: Restore dependencies
run: dotnet restore /p:TreatWarningsAsErrors=true
- name: Build
run: dotnet build --no-restore /p:TreatWarningsAsErrors=true
- name: Run Tests
run: dotnet test --no-build
nupkg:
name: Generate NuGet Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
name: Install Current .NET SDK
- name: Generate NuGet Packages
run: dotnet pack --configuration Release --output nupkg /p:TreatWarningsAsErrors=true
- uses: actions/upload-artifact@v2
if: success() && github.ref == 'refs/heads/main'
with:
name: nupkg
path: nupkg/*
retention-days: 1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/
obj/
20 changes: 20 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup Label="Shared NuGet Metadata">
<Authors>Polyadic</Authors>
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/polyadic/dispownership</PackageProjectUrl>
<Copyright>© Polyadic. All rights reserved.</Copyright>
</PropertyGroup>
<ItemGroup Label="Code Style">
<PackageReference Include="Messerli.CodeStyle" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup Label="Deterministic Builds and Source Link">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup Label="Deterministic Builds and Source Link">
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Build Dependencies">
<PackageVersion Include="Messerli.CodeStyle" Version="2.2.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
</ItemGroup>
<ItemGroup Label="Test Dependencies">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
</ItemGroup>
</Project>
21 changes: 21 additions & 0 deletions Dispownership.Sources/Dispownership.Sources.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.Build.NoTargets/3.7.0">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>contentFiles</ContentTargetFolders>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<LangVersion>11.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Dispownership\*.cs" Exclude="$(DefaultItemExcludes)" Link="%(FileName)%(Extension)" />
</ItemGroup>
<ItemGroup>
<Compile Update="@(Compile)">
<Pack>true</Pack>
<PackagePath>$(ContentTargetFolders)\cs\any\$(PackageId)\</PackagePath>
</Compile>
</ItemGroup>
</Project>
76 changes: 76 additions & 0 deletions Dispownership.Test/DisposableTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using Xunit;

namespace Dispownership.Test;

public sealed class DisposableTest
{
[Fact]
public void DisposesInnerValueIfOwned()
{
#pragma warning disable IDISP001
var value = new DisposableStub();
#pragma warning restore IDISP001

using (Disposable.Owned(value))
{
}

Assert.True(value.Disposed);
}

[Fact]
public void DoesNotDisposeInnerValueIfBorrowed()
{
#pragma warning disable IDISP001
var value = new DisposableStub();
#pragma warning restore IDISP001

using (Disposable.Borrowed(value))
{
}

Assert.False(value.Disposed);
}

[Fact]
public void DoesNotDisposeOwnedValueAfterMove()
{
#pragma warning disable IDISP001
var value = new DisposableStub();
#pragma warning restore IDISP001

using (var disposable = Disposable.Owned(value))
{
_ = disposable.Take();
}

Assert.False(value.Disposed);
}

[Fact]
public void ThrowsWhenMovingAValueTwice()
{
#pragma warning disable IDISP004
using var disposable = Disposable.Owned(new DisposableStub());
#pragma warning restore IDISP004
_ = disposable.Take();
Assert.Throws<InvalidOperationException>(() => disposable.Take());
}

[Fact]
public void ThrowsWhenMovingABorrowedValue()
{
#pragma warning disable IDISP004
using var disposable = Disposable.Borrowed(new DisposableStub());
#pragma warning restore IDISP004
Assert.Throws<InvalidOperationException>(() => disposable.Take());
}

private sealed class DisposableStub : IDisposable
{
public bool Disposed { get; private set; }

public void Dispose() => Disposed = true;
}
}
15 changes: 15 additions & 0 deletions Dispownership.Test/Dispownership.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dispownership\Dispownership.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
</Project>
50 changes: 50 additions & 0 deletions Dispownership.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dispownership", "Dispownership\Dispownership.csproj", "{0FC69541-37D7-4E58-A80B-2E8B8217A906}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dispownership.Sources", "Dispownership.Sources\Dispownership.Sources.csproj", "{B3926389-9766-4C06-ADDF-C46C0FFFDEE2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6FB7B031-CD30-4B19-A58C-23C4FE78FF0A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitattributes = .gitattributes
.gitignore = .gitignore
readme.md = readme.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Config", "Build Config", "{688F10E7-1CEB-412E-A9A8-8BD16F2BDED5}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
NuGet.config = NuGet.config
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dispownership.Test", "Dispownership.Test\Dispownership.Test.csproj", "{A1F6A514-4CC2-49E8-B4CF-D731692EE463}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0FC69541-37D7-4E58-A80B-2E8B8217A906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FC69541-37D7-4E58-A80B-2E8B8217A906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FC69541-37D7-4E58-A80B-2E8B8217A906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FC69541-37D7-4E58-A80B-2E8B8217A906}.Release|Any CPU.Build.0 = Release|Any CPU
{B3926389-9766-4C06-ADDF-C46C0FFFDEE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3926389-9766-4C06-ADDF-C46C0FFFDEE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3926389-9766-4C06-ADDF-C46C0FFFDEE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3926389-9766-4C06-ADDF-C46C0FFFDEE2}.Release|Any CPU.Build.0 = Release|Any CPU
{A1F6A514-4CC2-49E8-B4CF-D731692EE463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1F6A514-4CC2-49E8-B4CF-D731692EE463}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1F6A514-4CC2-49E8-B4CF-D731692EE463}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1F6A514-4CC2-49E8-B4CF-D731692EE463}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
72 changes: 72 additions & 0 deletions Dispownership/AsyncDisposable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma warning disable SA1201
#pragma warning disable IDE0005 // Disable unused usings warning if enabled in consuming project

#if (NETSTANDARD2_1 || NET5_0_OR_GREATER) && DISPOWNERSHIP_ASYNC
using System;
using System.Threading.Tasks;

namespace Dispownership;

#if DISPOWNERSHIP_VISIBILITY_PUBLIC
public
#else
internal
#endif
static class AsyncDisposable
{
/// <summary>Creates a owned wrapper around a disposable i.e. the disposable will be disposed when the wrapper is disposed.</summary>
public static AsyncDisposable<TDisposable> Owned<TDisposable>(TDisposable value)
where TDisposable : IAsyncDisposable
=> new(value, hasOwnership: true);

/// <summary>Creates a borrowing wrapper around a disposable i.e. the disposable will not be disposed when the wrapper is disposed.</summary>
public static AsyncDisposable<TDisposable> Borrowed<TDisposable>(TDisposable value)
where TDisposable : IAsyncDisposable
=> new(value, hasOwnership: false);
}

/// <summary>A wrapper around an <see cref="IAsyncDisposable"/> that either owns or borrows the value.
/// Use <see cref="AsyncDisposable.Owned{TDisposable}"/> or <see cref="AsyncDisposable.Borrowed{TDisposable}"/> to create an instance of this wrapper.</summary>
#if DISPOWNERSHIP_VISIBILITY_PUBLIC
public
#else
internal
#endif
struct AsyncDisposable<TDisposable> : IAsyncDisposable
where TDisposable : IAsyncDisposable
{
private readonly TDisposable _inner;
private bool _hasOwnership;

internal AsyncDisposable(TDisposable inner, bool hasOwnership)
{
_inner = inner;
_hasOwnership = hasOwnership;
}

public TDisposable Value => _inner;

/// <summary>Consumes the value leaving this wrapper without ownership.
/// This is useful in scenarios where you want to create a disposable, do some work that might fail and then return it.</summary>
/// <exception cref="InvalidOperationException">Thrown when this instance does not have ownership over the disposable.</exception>
public TDisposable Take()
{
if (!_hasOwnership)
{
throw new InvalidOperationException(
$"Value can only be consumed when owned. " +
$"This error may occur if you call {nameof(Take)} more than once");
}

_hasOwnership = false;
return _inner;
}

#pragma warning disable IDISP007
public ValueTask DisposeAsync()
=> _hasOwnership
? (_inner?.DisposeAsync() ?? default)
: default;
#pragma warning restore IDISP007
}
#endif
Loading

0 comments on commit 0d35512

Please sign in to comment.