SequelPay.DotNetPowerExtensions.Analyzers 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package SequelPay.DotNetPowerExtensions.Analyzers --version 1.0.3                
NuGet\Install-Package SequelPay.DotNetPowerExtensions.Analyzers -Version 1.0.3                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SequelPay.DotNetPowerExtensions.Analyzers" Version="1.0.3">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SequelPay.DotNetPowerExtensions.Analyzers --version 1.0.3                
#r "nuget: SequelPay.DotNetPowerExtensions.Analyzers, 1.0.3"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install SequelPay.DotNetPowerExtensions.Analyzers as a Cake Addin
#addin nuget:?package=SequelPay.DotNetPowerExtensions.Analyzers&version=1.0.3

// Install SequelPay.DotNetPowerExtensions.Analyzers as a Cake Tool
#tool nuget:?package=SequelPay.DotNetPowerExtensions.Analyzers&version=1.0.3                

DotNetPowerExtensions And Analyzer

What is the purpose of DotNetPowerExtensions and Analyzer?

  • To add functionality and diagnostics currently not available in .Net

1. Union<> Classes

Motivation

Sometimes we want to return a one of two possible values from a method, and of course it is not typesafe to return object, while returning a Tuple or other solutions might be too cumbersome

It is therefore useful to have a low ceremony typesafe struct Union<> taking 2 or 3 type arguments

Example Code
using DotNetPowerExtensions;

public interface IReturnType { string Name { get; set; }}
public class ReturnType1 : IReturnType { public string Name { get; set; }}
public class ReturnType2 : IReturnType { public string Name { get; set; }}

public Union<ReturnType1, RertunType2> TestMethod()
            => new Union<ReturnType1, RertunType2>(new ReturnType1 { Name = "Test" });

var retValue = TestMethod();
var n1 = retValue.First?.Name ?? retValue.Second!.Name; // n1 == "Test"
var n2 = retValue.As<IReturnType>().Name; // n2 == "Test"

2. NonDelegate

Decorate a method with [NonDelegate] in order to prevent it from being used as a callback and/or saved in a variable/property/argument. This is useful for Analyzers that rely on compile time static analysis (such as the ILocalFactory.Get() method, see below).

3. Dependency Attributes

You can now decorate your DI services with the an attribtue describing the service type, and insert all such classes in the DI at once,

Example Code
// [Singleton] // For a Singleton service
// [Scoped] // For a Scoped service
[Transient] // For a transient service
public class TestClass{}

And in your DI setup code, just have the following (where services is an IServiceCollection instance):

services.AddDependencies(); // That's it
3.1 For Base or Interface

If you want to register for a base or interface then just specify the desired types as parameters for the ctor (or the generic type in C# 11).

Example Code
public interface ITestClass {}

// Declaring service
// [Singleton(typeof(ITestClass))] // Or in C# 11 [Singleton<ITestClass>] // For a Singleton service
// [Scoped(typeof(ITestClass))] // Or in C# 11 [Scoped<ITestClass>] // For a Scoped service
// [Local(typeof(ITestClass))] // Or in C# 11 [Local<ITestClass>] // For a Local service, see below
[Transient(typeof(ITestClass))] // Or in C# 11 [Transient<ITestClass>] // For a transient service
public class TestClass : ITestClass {}

// Using service
// [Singleton] // For a Singleton service
// [Scoped] // For a Scoped service
// [Local] // For a Local service, see below
[Transient] // For a transient service
public class TestUserClass
{
    public TestUserClass(ITestClass testClass) {}
}
3.2 Generic Classes

You can register a closed generic version for an open generic by using the Use property

Example Code
public interface ITestClass {}

// Declaring service
// [Singleton(typeof(ITestClass), Use=typeof(TestClass<string>))] // Or in C# 11 [Singleton<ITestClass>(Use=typeof(TestClass<string>))] // For a Singleton service
// [Scoped(typeof(ITestClass), Use=typeof(TestClass<string>))] // Or in C# 11 [Scoped<ITestClass>(Use=typeof(TestClass<string>)] // For a Scoped service
// [Local(typeof(ITestClass), Use=typeof(TestClass<string>))] // Or in C# 11 [Local<ITestClass>(Use=typeof(TestClass<string>)] // For a Local service, see below
[Transient(typeof(ITestClass), Use=typeof(TestClass<string>))] // Or in C# 11 [Transient<ITestClass>(Use=typeof(TestClass<string>)] // For a transient service
public class TestClass<T> : ITestClass {}

// Using service
// [Singleton] // For a Singleton service
// [Scoped] // For a Scoped service
// [Local] // For a Local service, see below
[Transient] // For a transient service
public class TestUserClass
{
    public TestUserClass(ITestClass testClass) 
    {
        Assert.Equals(testClass.GetType(), typeof(TestClass<string>));
    }
}
3.3 ILocalFactory

Many times we just want an object to be local to a specific function instead of having an object for the entire lifetime of the object.

We can use for that ILocalFactory<> which is like a factory class and decorate the service with Local.

Example Code
// Declaring service
[Local]
public class TestClass : IDisposable { public void Dispose(){} }

// Using service
// [Singleton] // For a Singleton service
// [Scoped] // For a Scoped service
// [Local] // For a Local service
[Transient] // For a transient service
public class TestUserClass
{
    private ILocalFactory<TestClass> testClassFactory;
    public TestUserClass(ILocalFactory<TestClass> testClassFactory)
    {
        this.testClassFactory = testClassFactory;
    }

    public void SomeMethod()
    {
        using var testClass = testClassFactory.Get();
        // Do something
    }
}

4. MustInitialize

  • Allows you enforce that the given property or field has to be initialized when instantiated.
  • Removes the need to set a value when in a nullable context (in C# 8 and upwards) for such a property or field (NOTE: This only works in projects compatible with .Net Standard 2.0, as otherwise the functionality isn't available in Roslyn)
  • Also adds the ability to have DI services that the caller has to initialize before usage via ILocalFactory<>
Update for C#11

As C# 11 introduced the required keyword which has even more features than we have currently (but we hope to add and way more) then in general you should the new keyword instead.

However MustInitalize stil has a use even in C#11 in the following situations:

  • When you want to be able to suppress it (as in C#11 it is a compile error not a warning)
  • When you want to be able to use in generic class with the new() constraint (which isn't allowed in C#)
  • We can initialize in a DI service by using a LocaService<> and passing an anonymous object with the required properties
Example Code
public class TestClass
{
    [MustInitialize] public string TestProperty { get; set; } 
}
var testObj = new TestClass();

Without MustInitialize the following error will be reported on line 3:

warning CS8618: Non-nullable field 'TestProperty' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

However with MustInitialize it will report the following on line 5:

warning DNPE0103: Property 'TestProperty' must be initialized
4.1 DI

If the class containing the property/field decorated with MustInitialized is a service (i.e. it has one of the Singleton/Scoped/Transient attributes) it will warn that Local should be used instead.

And when using ILocalFactory to resolve the service the caller has to pass an anonymous object with the property names matching the properties/fields decorated with MustInitialize.

Note that it has to also match the casing of the name, and the value supplied has to match (at compile time) the compile time type of the orignal property/field, otheriwse a warning will be issued.

Example Code for DI service with ILocalFactory
// Declaring service
[Local]
public class TestClass
{
    [MustInitialize] public string TestProperty { get; set; } 
}

// Using service
// [Singleton] // For a Singleton service
// [Scoped] // For a Scoped service
// [Local] // For a Local service
[Transient] // For a transient service
public class TestUserClass
{
    private ILocalFactory<TestClass> testClassFactory;
    public TestUserClass(ILocalFactory<TestClass> testClassFactory)
    {
        this.testClassFactory = testClassFactory;
    }

    public void SomeMethod()
    {
        var testClass = testClassFactory.Get(new { TestProperty = "SomeString" });
        Assert.Equals(testClass.TestProperty, "SomeString");
    }
}

Componenets of MustInitialize Analyzer

  • A Roslyn analyzer which can be installed as a Nuget package
  • A Visual Studio extension

Installing as Nuget from local

Add in the same folder as the .sln file for your project a file named nuget.config with the following content (and replace Path/to/dll/folder with the actual path for the nupkg is stored)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="MyLocalSharedSource" value="Path/to/nupkg/folder" />
    </packageSources>
</configuration>

Afterwards it will show up as a package source in the Nuget Package sources, so you can install it from there

See sample image from the Visaul Studio Tools -> Nuget Package Manager -> Manage Nuget Packages for Solution... page

image

Debugging

To run in Visual Studio

Just load up the project nad hit start.

When running the project sometimes the breakpoints are not being hit, you can use the suggestions described in this issue: - Turn off Use 64 bit process for code analysis - Add a Debugger.Launch() statement

To run in dnSpy

Here are the steps involved:

  1. Open VS Develoepr Command Prompt and run MS build on your project

  2. From the output get the entire csc command line

    • Since it won't fit on the command line save the entire string (besides the csc path) in a file named options.rsp
  3. Open dnSpy

    • Use the 64 Bit version if you are on a x64 machine and csc is in Program Files not in Program Files (x86), otherwise use the 32 Bit version
  4. From File -> Open load all MustInitializeAnalyzer.dll files referenced in the string of step 2

    • Set breakpoints in all loaded files (to ensure that it is breaking)
  5. Run Start in dnSpy and set the following parameters:

    • Executable: Should be set to the csc path of step 2
    • Arguments: Should be @"options.rsp" (the full path of the file of step 2)
    • Working Directory: Should be set the directory of your .csproj file to be debugged
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.0.1 311 1/3/2024
4.0.0 331 9/8/2023 4.0.0 is deprecated because it has critical bugs.
3.0.5 342 8/29/2023
3.0.4 255 8/21/2023
3.0.1 296 8/8/2023
3.0.0 263 5/30/2023
2.0.0 274 5/4/2023
1.0.3 347 2/10/2023
1.0.1 354 1/16/2023
1.0.0 420 1/16/2023 1.0.0 is deprecated.

Summary of changes made in this release of the package.