Enumerar y cambiar, y qué les pasa

imagen



¿Con qué frecuencia ha tenido una situación en la que agregaría un nuevo valor a una enumeración y luego pasaría horas tratando de encontrar todos los lugares donde se usa, y luego agregar un nuevo caso para que no obtenga una ArgumentOutOfRangeException en tiempo de ejecución?



Idea



Si el único problema es la declaración de cambio y el seguimiento de los nuevos tipos, ¡eliminémoslos!



La idea es reemplazar el uso del interruptor con el patrón de visitantes.



Ejemplo 1



- API , , , .



DocumentType.cs:



public enum DocumentType
{
    Invoice,
    PrepaymentAccount
}

public interface IDocumentVisitor<out T>
{
    T VisitInvoice();
    T VisitPrepaymentAccount();
}

public static class DocumentTypeExt
{
    public static T Accept<T>(this DocumentType self, IDocumentVisitor<T> visitor)
    {
        switch (self)
        {
            case DocumentType.Invoice:
                return visitor.VisitInvoice();
            case DocumentType.PrepaymentAccount:
                return visitor.VisitPrepaymentAccount();
            default:
                throw new ArgumentOutOfRangeException(nameof(self), self, null);
        }
    }
}


, , .Net . .



visitor DatabaseSearchVisitor.cs:



public class DatabaseSearchVisitor : IDocumentVisitor<IDocument>
{
    private ApiId _id;
    private Database _db;

    public DatabaseSearchVisitor(ApiId id, Database db)
    {
        _id = id;
        _db = db;
    }

    public IDocument VisitInvoice() => _db.SearchInvoice(_id);
    public IDocument VisitPrepaymentAccount() => _db.SearchPrepaymentAccount(_id);
}


:



public void UpdateStatus(ApiDoc doc)
{
    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);

    var databaseDocument = doc.Type.Accept(searchVisitor);

    databaseDocument.Status = doc.Status;

    _db.SaveChanges();
}


2



, :



public enum PurseEventType
{
    Increase,
    Decrease,
    Block,
    Unlock
}

public sealed class PurseEvent
{
    public PurseEventType Type { get; }
    public string Json { get; }

    public PurseEvent(PurseEventType type, string json)
    {
        Type = type;
        Json = json;
    }
}


. visitor:



public interface IPurseEventTypeVisitor<out T>
{
    T VisitIncrease();
    T VisitDecrease();
    T VisitBlock();
    T VisitUnlock();
}

public sealed class PurseEventTypeNotificationVisitor : IPurseEventTypeVisitor<Missing>
{
    private readonly INotificationManager _notificationManager;
    private readonly PurseEventParser _eventParser;
    private readonly PurseEvent _event;

    public PurseEventTypeNotificationVisitor(PurseEvent @event, PurseEventParser eventParser, INotificationManager notificationManager)
    {
        _notificationManager = notificationManager;
        _event = @event;
        _eventParser = eventParser;
    }

    public Missing VisitIncrease() => Missing.Value;

    public Missing VisitDecrease() => Missing.Value;

    public Missing VisitBlock()
    {
        var blockEvent = _eventParser.ParseBlock(_event);
        _notificationManager.NotifyBlockPurseEvent(blockEvent);
        return Missing.Value;
    }

    public Missing VisitUnlock()
    {
        var blockEvent = _eventParser.ParseUnlock(_event);
        _notificationManager.NotifyUnlockPurseEvent(blockEvent);
        return Missing.Value;
    }
}


. Missing System.Reflection Unit. Result, , , .



:



public void SendNotification(PurseEvent @event)
{
    var notificationVisitor = new PurseEventTypeNotificationVisitor(@event, _eventParser, _notificationManager);
    @event.Type.Accept(notificationVisitor);
}






, , visitor . .



:



public static T Accept<TVisitor, T>(this DocumentType self, in TVisitor visitor)
    where TVisitor : IDocumentVisitor<T>
    {
        switch (self)
        {
            case DocumentType.Invoice:
                return visitor.VisitInvoice();
            case DocumentType.PrepaymentAccount:
                return visitor.VisitPrepaymentAccount();
            default:
                throw new ArgumentOutOfRangeException(nameof(self), self, null);
        }
    }


visitor , class struct.



, :



public void UpdateStatus(ApiDoc doc)
{
    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);

    var databaseDocument = doc.Type.Accept<DatabaseSearchVisitor, IDocument>(searchVisitor);

    databaseDocument.Status = doc.Status;

    _db.SaveChanges();
}


generic, , .



in-place



, visitor — . match.



:



public static T Match<T>(this DocumentType self, Func<T> invoiceCase, Func<T> prepaymentAccountCase)
{
    var visitor = new FuncVisitor<T>(invoiceCase, prepaymentCase);
    return self.Accept<FuncVisitor<T>, T>(visitor);
}


FuncVisitor:



public readonly struct FuncVisitor<T> : IDocumentVisitor<T>
{
    private readonly Func<T> _invoiceCase;
    private readonly Func<T> _prepaymentAccountCase;

    public FuncVisitor(Func<T> invoiceCase, Func<T> prepaymentAccountCase)
    {
        _invoiceCase = invoiceCase;
        _prepaymentAccountCase = prepaymentAccountCase;
    }

    public T VisitInvoice() => _invoiceCase();
    public T VisitPrepaymentAccount() => _prepaymentAccountCase();
}


match:



public void UpdateStatus(ApiDoc doc)
{
    var databaseDocument = doc.Type.Match(
        () => _db.SearchInvoice(doc.Id),
        () => _db.SearchPrepaymentAccount(doc.Id)
    );

    databaseDocument.Status = doc.Status;

    _db.SaveChanges();
}




enum :



  1. .
  2. .


, .

case switch.



, enum.








All Articles