Насколько я понимаю, Template method
это не то, что вы ищете. (Если вы не один из тех неприятных людей, которые используют ответы, не принимая их ;) )
Если вы хотите создать иллюзию свободы для пользователя и наказать ее за неправильное использование, это можно сделать следующим образом.
Определить атрибут:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class OrderAttribute : Attribute
{
public int Order { get; }
public OrderAttribute(int order) => Order = order;
}
Затем определите интерфейс:
public interface IObeyOrder
{
[Order(2)]
[Order(4)]
void Method1(); // should run 2nd or 4th
[Order(1)]
void Method2(); // 1st
[Order(3)]
void Method3(); // 3rd
void Method4(); // order doesn't matter
}
И реализовать его в классе, вызывая сначала CheckOrder()
в каждом методе:
public partial class ObeyOrder : IObeyOrder
{
public void Method1()
{
CheckOrder();
Console.WriteLine("Method1");
}
public void Method2()
{
CheckOrder();
Console.WriteLine("Method2");
}
public void Method3()
{
CheckOrder();
Console.WriteLine("Method3");
}
public void Method4()
{
CheckOrder();
Console.WriteLine("Method4");
}
public void Method5() // non-interface
{
CheckOrder();
Console.WriteLine("Method5");
}
}
, где CheckOrder()
:
public partial class ObeyOrder : IObeyOrder
{
private static readonly Dictionary<string, int[]> orderedMethods = OrderHelper<IObeyOrder>.OrderedMethods;
private readonly Queue<int> orders = new Queue<int>(orderedMethods.Values.SelectMany(i => i).OrderBy(i => i));
private void CheckOrder([CallerMemberName] string methodName = "")
{
if (!orderedMethods.TryGetValue(methodName, out var methodOrders))
return;
var order = orders.Peek();
if (!methodOrders.Contains(order))
throw new Exception($"Wrong method call order. Method '{methodName}' with orders [{string.Join(", ", methodOrders)}]. Expected order {order}.");
orders.Enqueue(orders.Dequeue());
}
}
Конечно, вы можете сделать это в неполном классе.
public static class OrderHelper<T>
{
public static Dictionary<string, int[]> OrderedMethods { get; } = typeof(T)
.GetMethods()
.Select(method => new
{
Method = method.Name,
Orders = method.GetCustomAttributes(typeof(OrderAttribute), false)
.Cast<OrderAttribute>()
.Select(attribute => attribute.Order)
.ToArray()
})
.Where(method => method.Orders.Length > 0)
.ToDictionary(method => method.Method, method => method.Orders);
}
Usage:
var obeyOrder = new ObeyOrder();
obeyOrder.Method2(); // should go 1st
obeyOrder.Method4(); // can go whenever, since there is no order attribute
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method5(); // can go whenever, since it's non-interface
obeyOrder.Method3(); // should go 3rd
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method2(); // should go 1st (after the last had been already called)
отлично работает, но
var obeyOrder = new ObeyOrder();
obeyOrder.Method2(); // should go 1st
obeyOrder.Method4(); // can go whenever, since there is no order attribute
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method5(); // can go whenever, since it's non-interface
obeyOrder.Method3(); // should go 3rd
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method2(); // should go 1st (after the last had been already called)
obeyOrder.Method2(); // should throw since the 2nd (obeyOrder.Method1()) is expected
бросает
Неправильный порядок вызова метода. Метод 'Method2' с заказами [1]. Ожидаемый заказ 2.