Com que frequência você já teve uma situação em que adiciona um novo valor a um enum e, em seguida, passa horas tentando encontrar todos os lugares onde ele é usado e, em seguida, adiciona um novo caso para não obter um ArgumentOutOfRangeException no tempo de execução?
Idéia
Se o único problema for a instrução switch e manter o controle de novos tipos, vamos nos livrar deles!
A ideia é substituir o uso de switch pelo padrão de visitante.
Exemplo 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 :
- .
- .
, .
case switch.
, enum.