Вы когда-нибудь добавляли новое значение в перечисление, а затем тратили часы, пытаясь найти все места, где оно используется, а затем добавляли новый случай, чтобы избежать получения исключения ArgumentOutOfRangeException во время выполнения?
Идея
Если проблема заключается только в операторе переключения и отслеживании новых типов, то давайте избавимся от них! Идея состоит в том, чтобы заменить использование переключателя шаблоном посетителя.
Пример 1
Предположим, у нас есть какой-то API для работы с документами, из которого мы получаем необходимые данные и определяем их тип, а затем, в зависимости от этого типа, необходимо выполнять различные операции.Давайте определим файл ТипДокумента.
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-разработчика.
Но иногда это очень ухудшается делает код более понятным.
Опишем посетителя, который будет искать в базе данных нужный документ. База данныхSearchVisitor.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;
}
}
Мы хотим отправлять пользователю уведомления о событиях определенного типа.
Затем мы реализуем посетителя: 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);
}
Добавление
Если вам нужно это быстрее
Если вам нужно использовать этот подход там, где важна производительность, вы можете использовать структуры в качестве посетителя.Тогда код изменится следующим образом.
Метод расширения: 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);
}
}
Сам посетитель остается прежним, мы просто меняем класс на 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();
}
При таком использовании дженерика типы необходимо указывать самостоятельно, так как компилятор этого не делает. хочет может отображать их автоматически.
Читабельность и реализация на месте
Если вам нужно реализовать логику только в одном месте, то посетитель часто оказывается громоздким и неудобным.Поэтому есть альтернативное решение соответствовать .
Просто пример со структурой: 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();
}
Применение соответствовать : public void UpdateStatus(ApiDoc doc)
{
var databaseDocument = doc.Type.Match(
() => _db.SearchInvoice(doc.Id),
() => _db.SearchPrepaymentAccount(doc.Id)
);
databaseDocument.Status = doc.Status;
_db.SaveChanges();
}
Нижняя граница
При добавлении нового значения в перечисление вы должны:- Добавьте метод в интерфейс.
- Добавьте его использование в метод расширения.
Это все еще не панацея, но может оказаться большим подспорьем при работе с перечислениями.
Ссылки
- https://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/
- https://en.wikipedia.org/wiki/Unit_type
- https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/results
NET #шаблон проектирования #enum #программирование #расширение #посетитель
-
Оптический Удлинитель Hdmi. 300 Метров
19 Oct, 24 -
Видео: Знакомство С Mps
19 Oct, 24 -
Oauth С Использованием Jwt В Отделе Продаж
19 Oct, 24 -
Ошибка Windows 2003
19 Oct, 24