我决定使用CRTP重做房间中对象的 OOP 实现。
using System;
using System.Linq;
using System.Reflection;
using System.Collections;
using System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Method)]
public class ActionItemAttribute : Attribute {
public string ActionName { get; set; }
public string ActionDescription { get; set; }
}
// если сделать реализацию немножко по-другому, то без этого класса можно обойтись
public class ActionItem {
public IList<CustomAttributeNamedArgument> Attributes { get; init; }
public string MethodName { get; init; }
public ActionItem(string mn, IList<CustomAttributeNamedArgument> il) { MethodName = mn; Attributes = il; }
}
class ItemBase {
public string Name { get; init; }
public ItemBase(string nm) => Name = nm;
// это вспомогательная функция, можно и без неё
protected string GetActionDescription([CallerMemberName] string callingMethod = "")
=> (GetType().GetMethod(callingMethod)!.GetCustomAttribute(typeof(ActionItemAttribute)) as ActionItemAttribute)
?.ActionDescription!;
// это нужно
public virtual List<ActionItem> ActionableItems {get => null; }
// это нужно
public object Act(string action) {
Console.Write($" {((ActionItemAttribute)Attribute.GetCustomAttribute(GetType().GetMethod(action), typeof(ActionItemAttribute), true)).ActionName}: ");
if (GetType().GetMethod(action).ReturnType != typeof(void))
return GetType().GetMethod(action).Invoke(this, new object[] { });
else { GetType().GetMethod(action).Invoke(this, new object[] { }); return null; }
}
// каждое действие должно быть помечено таки атрибутом, он может быть и пустой,
// если дополнительная информация не нужна
[ActionItem(ActionName = "Взять", ActionDescription = "Взять предмет")]
public virtual void Take() => Console.WriteLine("Не забудь вернуть обратно.");
[ActionItem(ActionName = "Вытащить содержимое", ActionDescription = "Вытащить содержимое из предмета")]
public virtual object GetContent() { Console.WriteLine("Аааа, ломать не нужно!!!"); return null; }
}
class Item<TSelf> : ItemBase where TSelf : Item<TSelf> {
protected static List<ActionItem> _sactionlist;
// это нужно для построения спика действий
protected static List<ActionItem> GetActionList(Type T) =>
T.GetMethods().Where(w => w.CustomAttributes
.Where(w => w.AttributeType == typeof(ActionItemAttribute)).Any())
.Select(s => new ActionItem(s.Name, s.CustomAttributes
.Single(s => s.AttributeType == typeof(ActionItemAttribute)).NamedArguments))
.ToList();
public override List<ActionItem> ActionableItems { get => _sactionlist; }
public Item(string nm) : base(nm) { }
// это вспомогательная функция, можно и без неё
}
class WindowItem : Item<WindowItem> {
static WindowItem() => _sactionlist = GetActionList(MethodBase.GetCurrentMethod().DeclaringType);
public WindowItem(string name) : base(name) { }
[ActionItem(ActionName = "Открыть", ActionDescription = "Открыть окно")]
public void Open() => Console.WriteLine(GetActionDescription());
[ActionItem(ActionName = "Закрыть", ActionDescription = "Закрыть окно")]
public void Close() => Console.WriteLine(GetActionDescription());
[ActionItem(ActionName = "Взять", ActionDescription = "Взять предмет")]
public override void Take() => Console.WriteLine("Ага, попробуй возьми.");
}
class Cloth : Item<Cloth> {
static Cloth() => _sactionlist = GetActionList(MethodBase.GetCurrentMethod().DeclaringType);
public Cloth(string name) : base(name) { }
public void Wash() => Console.WriteLine("Постирать тряпку.");
}
class Box : Item<Box> {
static Box() => _sactionlist = GetActionList(MethodBase.GetCurrentMethod().DeclaringType);
public Box(string name) : base(name) { }
[ActionItem(ActionName = "Вытащить содержимое", ActionDescription = "Вытащить содержимое из коробки")]
public override object GetContent() => new object[] { "большие солнечные очки", "звонок от велосипеда", "вобла" };
}
class Room : IEnumerable {
List<ItemBase> _items;
public Room() => _items = new List<ItemBase>();
public void Add(ItemBase i) => _items.Add(i);
public IEnumerator GetEnumerator() => _items.GetEnumerator();
}
internal class Program {
static void ConsoleWriteLine(object o) =>
Console.WriteLine(o is IEnumerable ? String.Join("; ", (object[])o) : o);
static void Main() {
var room = new Room { new WindowItem("Переднее окно"), new Cloth("Тряпка для доски"), new Box("Шкатулка") };
foreach (ItemBase itm in room) {
Console.WriteLine($"Предмет: {itm.Name}");
foreach (var method in itm.ActionableItems)
if (itm.GetType().GetMethod(method.MethodName).ReturnType != typeof(void))
ConsoleWriteLine(itm.Act(method.MethodName));
else itm.Act(method.MethodName);
}
}
}
我将 _sactionlist 字段移至基类,但我无法摆脱每个后代中的静态构造函数(静态 WindowItem、静态 Cloth、静态 Box)。还有出路吗?
由于我们在 CRTP 知道派生类的“真实”类型,因此它应该如何工作: