SequelPay.DotNetPowerExtensions.Analyzers
1.0.3
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
<PackageReference Include="SequelPay.DotNetPowerExtensions.Analyzers" Version="1.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add SequelPay.DotNetPowerExtensions.Analyzers --version 1.0.3
#r "nuget: SequelPay.DotNetPowerExtensions.Analyzers, 1.0.3"
// 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
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:
Open VS Develoepr Command Prompt and run MS build on your project
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
- Since it won't fit on the command line save the entire string (besides the csc path) in a file named
Open dnSpy
- Use the 64 Bit version if you are on a x64 machine and csc is in
Program Files
not inProgram Files (x86)
, otherwise use the 32 Bit version
- Use the 64 Bit version if you are on a x64 machine and csc is in
From
File -> Open
load allMustInitializeAnalyzer.dll
files referenced in the string of step 2- Set breakpoints in all loaded files (to ensure that it is breaking)
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
- Executable: Should be set to the
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.
Summary of changes made in this release of the package.