Skip to content

Commit

Permalink
Merge pull request #7 from raffaeler/ImplementInterface
Browse files Browse the repository at this point in the history
Implement interface
  • Loading branch information
raffaeler authored Aug 13, 2023
2 parents 4f905d6 + cfb1652 commit f0bff07
Show file tree
Hide file tree
Showing 29 changed files with 2,469 additions and 1,470 deletions.
11 changes: 6 additions & 5 deletions Package.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# SpeedyGenerators

C# code generators are new to C# version 9.
SpeedyGenerators is a collection of C# Code Generators (available since C# 9) which generate C# code to *augment* the code written by the developer.

Apart from straightforward cases, the greatest majority of the code is generated using the Roslyn (C# compiler) API.
### List of generators

All the code in the NuGet package is only used at development time. There is no run-time dependency, therefore you don't need to deploy the binaries for this package.
* `MakePropertyAttribute` is applied to a field and generates (in the partial class) a property implementing the `INotifyPropertyChanged` interface.
* (new in version 1.1.0) `MakeConcreteAttribute` is applied to a partial class and generated (in the partial class) all the properties belonging to the interface specified in the attribute (full qualified name of the interface). The interface may or not be implemented by the class.

Please visit https://github.com/raffaeler/SpeedyGenerators for more details.

The generation process happens thanks to the C# compiler. This means it works even if the build is done on the command line. In other word there is no dependency from Visual Studio or other IDEs.

Once the package is referenced in the application, just start coding as shown in the examples. The generated code can be examined by expanding the Analyzers tree. See the repository readme for details.

116 changes: 106 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@

![Nuget](https://img.shields.io/nuget/v/SpeedyGenerators) ![Nuget](https://img.shields.io/nuget/dt/SpeedyGenerators) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/raffaeler/SpeedyGenerators) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/raffaeler/SpeedyGenerators)

C# code generators are new to C# version 9.
SpeedyGenerators is a collection of C# Code Generators (available since C# 9) which generate C# code to *augment* the code written by the developer.

### List of generators

* `MakePropertyAttribute` is applied to a field and generates (in the partial class) a property implementing the `INotifyPropertyChanged` interface.
* (new in version 1.1.0) `MakeConcreteAttribute` is applied to a partial class and generated (in the partial class) all the properties belonging to the interface specified in the attribute (full qualified name of the interface). The interface may or not be implemented by the class.

### How does this work

Apart from straightforward cases, the greatest majority of the code is generated using the Roslyn (C# compiler) API.

All the code in the NuGet package is only used at development time. There is no run-time dependency, therefore you don't need to deploy the binaries for this package.
The attributes needed to trigger the generation are also generated. This means that:

The generation process happens thanks to the C# compiler. This means it works even if the build is done on the command line. In other word there is no dependency from Visual Studio or other IDEs.
* `SpeedyGenerators` is not a run-time dependency.
* `SpeedyGenerators` assembly binary is never needed in the deployment folder
* The code inside the `SpeedyGenerators` assembly is only invoked during the compilation process (which also happens inside the IDEs leveraging the *Analyzers* features)
* The attributes required trigger the code generation are always generated in the assembly referencing `SpeedyGenerators`. You can see the code using ILSpy or an equivalent tool.
* A potential future breaking change is its namespace. The current namespace for the attribute is `SpeedyGenerators` but I am thinking to change it to the root namespace of the target process.

Once the package is referenced in the application, just start coding as shown in the examples. The generated code can be examined by expanding the Analyzers tree:

![image-20211026173825724](images/README/image-20211026173825724.png)
The generation process happens thanks to the a feature provided by the C# compiler. This means it works even if the build is done on the command line. This means that it will work in the CI process as well.

## Implementing `INotifyPropertyChanged`
Once the package is referenced in the application, just start coding as shown in the examples. The generated code can be examined by expanding the Analyzers tree:

The first generator involved is the `MakePropertyAttribute` which is always generated so that it can be used without needing any runtime dependency.
![image-20211026173825724](images/README/image-20211026173825724.png)

A potential future breaking change is its namespace. The current namespace for the attribute is `SpeedyGenerators` but I am thinking to change it to the root namespace of the target process.
## `MakePropertyAttribute`

The second generator is the one producing the code to implement the `INotifyPropertyChanged` interface.
This (generated) attribute will trigger the implementation of the `INotifyPropertyChanged` interface.

The code you are expected to write in your class is the following:

Expand Down Expand Up @@ -165,7 +175,7 @@ public string? Description

The optional parameters can be, of course, combined together.

### Final Notes
#### Final Notes

* The generated code supports adding the `using` declaration for types in other namespaces. For example, if the type is a `ObservableCollection`, the `using System.Collections.ObjectModel;` is added automatically.

Expand All @@ -182,6 +192,92 @@ The optional parameters can be, of course, combined together.
If that is found, the generated code in the property setters will invoke that method.
If that method is not found (because it has a different name), the generator will call anyway the **non-existent** `OnPropertyChanged` method. This method **is required** and must be manually written by the developer. The code inside this method should just call the method in the base class that can raise the event.



## `MakeConcreteAttribute`

> This Attribute is available starting from version 1.1.0
The `MakeConcreteAttribute` is meant to implement all the properties of an interface inside a class.

For example, let's say we have the following interface and we want to automatically implement those three properties in a class

```CSharp
namespace LibraryTest
{
namespace Abc
{
interface IMyInterface
{
string Name { get; }
StringBuilder Other { get; }
int Age { get; }
}
}
}
```

The code the user would write is:

```CSharp
[MakeConcrete(
interfaceFullTypeName: "LibraryTest.Abc.IMyInterface",
generateInitializingConstructor: true,
makeSettersPrivate: false,
implementInterface: false,
makeReferenceTypesNullable: true,
makeValueTypesNullable: false)]
partial class MyClass
{
}
```

> Only 'interfaceFullTypeName' is a required argument, the others are optional
The generated code will be:

```CSharp
namespace LibraryTest
{
partial class MyClass
{
public MyClass(string? name, StringBuilder? other, int age)
{
Name = name;
Other = other;
Age = age;
}


/// <summary>
/// Implements IMyInterface.Name
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Implements IMyInterface.Other
/// </summary>
public StringBuilder? Other { get; set; }

/// <summary>
/// Implements IMyInterface.Age
/// </summary>
public int Age { get; set; }
};
}
```

The attribute parameters are:

* `interfaceFullTypeName`. This string specify the full type of the interface to implement. This parameter is a string to avoid dependencies over the assembly containing the interface so that it can be dynamically loaded at runtime.
* `generateInitializingConstructor`. As for the example above, this will trigger the generation of the initialization constructor specifying all the properties defined in the interface.
* `makeSettersPrivate`. This specifies whether the properties should be private or public.
* `implementInterface`. When true, this parameter will make the partial class to derive the specified interface.
* `makeReferenceTypesNullable`. This specify whether or not the properties whose type is a **reference-type** should be implemented as nullable.
* `makeValueTypesNullable`. This specify whether or not the properties whose type is a **value-type** should be implemented as nullable.



## Issues

Please use the Issues on the GitHub repository to provide bugs and suggestions.
Expand Down
38 changes: 38 additions & 0 deletions src/LibraryTest/Impl1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using SpeedyGenerators;

namespace LibraryTest
{
namespace Abc
{
interface IMyInterface
{
string Name { get; }
StringBuilder Other { get; }
int Age { get; }
}
}

[MakeConcrete(
interfaceFullTypeName: "LibraryTest.Abc.IMyInterface",
generateInitializingConstructor: true,
makeSettersPrivate: false,
implementInterface: false,
makeReferenceTypesNullable: true,
makeValueTypesNullable: false)]
partial class MyClass
{
}

partial record class MyRecord { }

partial struct MyStruct { }

partial record struct MyRecordStruct { }

}
2 changes: 1 addition & 1 deletion src/LibraryTest/LibraryTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<ProjectReference Include="..\SpeedyGenerators\SpeedyGenerators.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
Loading

0 comments on commit f0bff07

Please sign in to comment.