Novidades do C# 8.0: Default interface methods

Olá, neste artigo eu vou escrever sobre a novidade do C# 8 default interface methods. Neste artigo você poderá ver um resumo das novidades que eu acho mais relevantes que vieram no C# 8.

Antes do C# 8

C#
public interface ILimit
{
    IFilterBuilder Limit(in uint limit);
}

public interface ISkip
{
    IFilterBuilder Skip(in uint skip);
}

public interface ISelect
{
    IFilterBuilder Select(params string[] fields);
}

public interface IFilterBuilder : ILimit, ISkip, ISelect
{
    string Build();
}

public sealed class FilterBuilder : IFilterBuilder
{
    private readonly ICollection<string> filters;

    private FilterBuilder() => this.filters = new List<string>();

    public IFilterBuilder Skip(in uint skip)
    {
        var newSkip = skip < 1
            ? 1
            : skip;

        this.filters.Add($"filter[skip]={newSkip}");
        return this;
    }

    public IFilterBuilder Limit(in uint limit)
    {
        var newLimit = limit < 1
            ? 1
            : limit;

        this.filters.Add($"filter[limit]={newLimit}");
        return this;
    }

    public IFilterBuilder Select(params string[] fields)
    {
        foreach (var field in fields)
        {
            this.filters.Add($"filter[fields][{field}]=true");
        }

        return this;
    }

    public string Build() => string.Join("&", this.filters);

    public static IFilterBuilder NewFilterBuilder() => new FilterBuilder();
}

Antes do C# 8 (uma opção com interface extensions)

Este exemplo foi inspirado no artigo Pseudo Traits in C#, que traz uma abordagem de como podemos fazer praticamente a mesma coisa sem precisar desta nova funcionalidade.

C#
public interface IFilterBase
{
    IFilterBuilder AddFilters(params string[] values);
}

public interface ILimit : IFilterBase
{
}

public static partial class FilterExtension
{
    public static IFilterBuilder Limit(this ILimit @this, uint limit)
    {
        var newLimit = limit < 1
            ? 1
            : limit;

        return @this
            .AddFilters($"filter[limit]={newLimit}");
    }
}

public interface ISkip : IFilterBase
{
}

public static partial class FilterExtension
{
    public static IFilterBuilder Skip(this ISkip @this, uint skip)
    {
        var newSkip = skip < 1
            ? 1
            : skip;

        return @this
            .AddFilters($"filter[skip]={newSkip}");
    }
}

public interface ISelect : IFilterBase
{
}

public static partial class FilterExtension
{
    public static IFilterBuilder Select(this ISelect @this, params string[] fields) => @this
        .AddFilters(fields
            .Select(field => $"filter[fields][{field}]=true")
            .ToArray());
}

public interface IFilterBuilder : ILimit, ISkip, ISelect
{
    string Build();
}

public sealed class FilterBuilder : IFilterBuilder
{
    private readonly ICollection<string> filters;

    private FilterBuilder() => this.filters = new List<string>();

    public string Build() => string.Join("&", this.filters);

    public IFilterBuilder AddFilters(params string[] values)
    {
        foreach (var value in values)
        {
            this.filters.Add(value);
        }

        return this;
    }

    public static IFilterBuilder NewFilterBuilder() => new FilterBuilder();
}

Depois do C# 8

C#
public interface IFilterBase
{
    IFilterBuilder AddFilters(params string[] values);
}

public interface ISkip : IFilterBase
{
    IFilterBuilder Skip(in uint skip)
    {
        var newSkip = skip < 1
            ? 1
            : skip;

        return this
            .AddFilters($"filter[skip]={newSkip}");
    }
}

public interface ILimit : IFilterBase
{
    IFilterBuilder Limit(in uint limit)
    {
        var newLimit = limit < 1
            ? 1
            : limit;

        return this
            .AddFilters($"filter[limit]={newLimit}");
    }
}

public interface ISelect : IFilterBase
{
    public IFilterBuilder Select(params string[] fields) => this
        .AddFilters(fields
            .Select(field => $"filter[fields][{field}]=true")
            .ToArray());
}

public interface IFilterBuilder : ILimit, ISkip, ISelect
{
    string Build();
}

public sealed class FilterBuilder : IFilterBuilder
{
    private readonly ICollection<string> filters;

    private FilterBuilder() => this.filters = new List<string>();

    public string Build() => string.Join("&", this.filters);

    public IFilterBuilder AddFilters(params string[] values)
    {
        foreach (var value in values)
        {
            this.filters.Add(value);
        }

        return this;
    }

    public static IFilterBuilder NewFilterBuilder() => new FilterBuilder();
}

Considerações finais

Esta nova funcionalidade dividiu um pouco a opinião da comunidade, alguns gostaram e outros nem tanto, abaixo dois artigos que ilustram bem essas opiniões:

O último exemplo com a implementação desta nova funcionalidade foi retirado do projeto Http.Query.Filter.

Obrigado pela visita 🙂!

Comentários