DuckInterface 0.3.0

dotnet add package DuckInterface --version 0.3.0                
NuGet\Install-Package DuckInterface -Version 0.3.0                
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="DuckInterface" Version="0.3.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add DuckInterface --version 0.3.0                
#r "nuget: DuckInterface, 0.3.0"                
#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 DuckInterface as a Cake Addin
#addin nuget:?package=DuckInterface&version=0.3.0

// Install DuckInterface as a Cake Tool
#tool nuget:?package=DuckInterface&version=0.3.0                

DuckInterface

This repository contains my attempt to enable duck typing support in C#. It is powered by Roslyn and the new C# 9 feature the Source Generators. I would say it is purely academic/just for fun stuff, but for some scenarios, it can be useful.

Nuget

How to use it

Let's suppose that you have the next declaration:

public interface ICalculator
{
  float Calculate(float a, float b);
}

public class AddCalculator
{

  public float Calculate(float a, float b);
}

It is important to notice that the AddCalculator doesn't implement the ICalculator in any way. It just has an identical method declaration. If we try to use it like in the next snippet, we will get a compilation error:

var addCalculator = new AddCalculator();

var result = ApplyCalculator(addCalculator, 10, 20);

float ApplyCalculator(ICalculator calculator, float a, float b)
{
  return calculator.Calculate(a, b);
}

In this case, duck typing can be helpful because it will allow us to pass AddCalculator easily. The DuckInterface may help with it. You will need to install the NuGet package and call the Duck<> extension method where we are passing the variable:

var calculator = new AddCalculator();
var result = ApplyCalculator(calculator.Duck<ICalculator>(), 10, 20);

Console.WriteLine($"Result: {result}");

static float ApplyCalculator(ICalculator calculator, float a, float b)
{
    return calculator.Calculate(a, b);
}

And it's done. The compilation errors are gone, and everything works as expected.

How it works

There is a source generator that looks for a method call and variable assignments to understand how the duckable interface may be used. For example, let's look for the next snippet:

var result = ApplyCalculator(addCalculator.Duck<ICalculator>(), 10, 20);

The analyzer will see that the Duck extension method is utilized, and then it will check the type of addCalculator variable. If the type has all the required members, the source generator will generate a container and new extension method with concrete type:

public static class Duck_expDuckable_ICalculator_expDuckable_AddCalculator_Extensions
{
    public static TInterface Duck<TInterface>(this global::expDuckable.AddCalculator value) 
        where TInterface: class
    {
        return new Duck_expDuckable_ICalculator(value) as TInterface;
    }
}

public static class DuckHandlerExtensionsForexpDuckable_ICalculator
{

    public static global::expDuckable.ICalculator Create(this IDuckHandler<global::expDuckable.ICalculator> handler, Func<float, float, float> calculate)
    {
        return new DuckInterface.Generated.expDuckable.Duck_expDuckable_ICalculator(calculate);
    }

    public static global::expDuckable.ICalculator CreatePartial(this IDuckHandler<global::expDuckable.ICalculator> handler, Func<float, float, float> calculate = default)
    {
        return new DuckInterface.Generated.expDuckable.Duck_expDuckable_ICalculator(calculate);
    }


    public static global::expDuckable.ICalculator Make(this IDuckHandler<global::expDuckable.ICalculator> handler, Func<float, float, float> calculate)
    {
        return new DuckInterface.Generated.expDuckable.Duck_expDuckable_ICalculator(calculate);
    }

    public static global::expDuckable.ICalculator MakePartial(this IDuckHandler<global::expDuckable.ICalculator> handler, Func<float, float, float> calculate = default)
    {
        return new DuckInterface.Generated.expDuckable.Duck_expDuckable_ICalculator(calculate);
    }
}

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public partial class Duck_expDuckable_ICalculator: global::expDuckable.ICalculator 
{
    public Duck_expDuckable_ICalculator(Func<float, float, float> calculate)
    {
        _Calculate = calculate;
    }

    [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 
    private readonly Func<float, float, float> _Calculate;        


    [System.Diagnostics.DebuggerStepThrough]
    public float Calculate(float a, float b)
    {
        return _Calculate(a, b);
    }
}

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has 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
0.3.0 68 1/4/2025
0.2.1.2 545 11/11/2021
0.2.1.1 435 10/24/2021
0.2.1 361 10/24/2021
0.2.0 402 10/24/2021
0.1.5 392 8/17/2021
0.1.4 337 8/17/2021
0.1.3 563 1/3/2021
0.1.2 424 1/1/2021
0.1.1 430 1/1/2021
0.1.0 430 1/1/2021