问题就在下面代码的注释中 顺便说一句,此代码仅对 C#-11 语言版本有效 (即:创建一个Net 8.0框架项目或更高版本进行测试)。
以下代码的问题是否是新的C#语法的限制 (这会很悲伤,因为我猜这个用例是最想要的用例之一)? 还是我语法错了?
我的目标是稍后在仅在运行时已知的类型上使用“IDynamicEnum”(通过使用反射)。但我不确定静态接口方法是否应该在我尝试在这里使用,或者是否仅用于 T 在编译时始终已知的用例。我只是在玩 C# 11
我知道我可以完全删除接口 IDynamicEnum 并使用类似
typeof(IDynamicEnum<>).MakeGenericType(the_type_is_got_by_reflection)
的东西,但这个问题无论如何看起来都很有趣。
/// <summary> Tag a class that acts as an enum class (in the java way) </summary>
public interface IDynamicEnum
{
string Name { get; }
ulong Value { get; }
static abstract IEnumerable<object> GetAllValues();
}
/// <inheritdoc cref="IDynamicEnum"/>
public interface IDynamicEnum<out T> : IDynamicEnum
where T : class, IDynamicEnum<T>
{
// A better signature: We strongly type
new static abstract IEnumerable<T> GetAllValues();
// I would like to automatically implement the non generic method...
// To do that, intuitively i would like to write this line
// which is kind of a "return type covariance on static interface method"
// But sadly it gives error CS8926
//static IEnumerable<object> IDynamicEnum.GetAllValues() => GetAllValues();
}
public class MyDynamicEnum : IDynamicEnum<MyDynamicEnum>
{
public string Name { get; }
public ulong Value { get; }
public static MyDynamicEnum Foo { get; } = new MyDynamicEnum("Foo", 1);
public static MyDynamicEnum Bar { get; } = new MyDynamicEnum("Bar", 2);
public static MyDynamicEnum Baz { get; } = new MyDynamicEnum("Baz", 3);
public static IEnumerable<MyDynamicEnum> AllValues { get; } = new[] { Foo, Bar, Baz };
protected MyDynamicEnum(string name, long value) { Name = name; Value = value; }
public static IEnumerable<MyDynamicEnum> GetAllValues() => AllValues;
// So I could get rid of this annoying / duplicated line
static IEnumerable<object> IDynamicEnum.GetAllValues() => AllValues;
}
编辑:仅当上面的抽象问题对您来说不够时才继续阅读
真正的应用是用于事先不知道的枚举类型,例如处理与解析 csproj/sln 文件相关的所有微小值集:
因此,要拥有“正确的”算法,最好有一种方法可以简单地识别它们,而无需每次都处理它们的所有书写形式。实际上,这更像是一种 Flyweigh 模式,而不是动态枚举概念。无论如何,如果用户想要扩展 DynamicEnum 类型以添加“net7.0-android”的支持并在其代码中创建静态单例实例以通过他的算法引用它,我的代码不应阻止他这样做
所以..好吧,“DynamicEnum”这个名字可能不正确。但我想问一个简单的问题,而不是参与关于 XY 问题的辩论。
而且我希望这段代码能够在 net48 和 net8.0 中工作,所以我利用编译器,因为它会检查代码是否针对 bioth net8.0 和 net4.8 进行编译。因此,静态方法约束给了我更大的可能性,该方法也存在于 net48 中(我不能保证,但比期望开发人员阅读 xml 文档的可能性更大......)
我当前的解决方案如下所示:
// comment for stackoverflow: this interface looks simple / intuitive
public interface IDynamicEnum
{
/// <summary> Represent the culture invariant technical representation name </summary>
string NormalizedName { get; }
/// <summary> The value of the enum (if supported, see MaxValueRepresentation) </summary>
ulong Value { get; }
}
// comment for stackoverflow: This interface adds "behavior" allowed by new C# 11 syntax
public interface IDynamicEnum<out T> : IDynamicEnum
where T : class, IDynamicEnum<T>
{
#if NET8_0_OR_GREATER
static abstract ulong? MaxValueRepresentation { get; }
static abstract IEnumerable<T> GetAllValues();
// Is it not possible to write something like this (that does not cause a CS8926 error) ?
//static IEnumerable<IDynamicEnum> IDynamicEnum.GetAllValues() => GetAllValues();
#endif
}
// comment for stackoverflow: this interface is the helper class that works both for net48/net8.0
public static class IDynamicEnum_Extensions
{
#if NET8_0_OR_GREATER
public static IEnumerable<T> GetAllValues<T>()
where T: class, IDynamicEnum<T>
{
return T.GetAllValues();
}
#else
public static IEnumerable<T> GetAllValues<T>()
where T : class, IDynamicEnum<T>
=> (IEnumerable<T>)_GetAllValues(typeof(T));
#endif
public static IEnumerable<IDynamicEnum> GetAllValues(Type dynamicEnumType)
{
var interfaceType = typeof(IDynamicEnum<>).MakeGenericType(dynamicEnumType);
if (!interfaceType.IsAssignableFrom(dynamicEnumType))
throw new ArgumentException($"Type {dynamicEnumType} is not implementing {interfaceType}", nameof(dynamicEnumType));
return (IEnumerable < IDynamicEnum > )_GetAllValues(dynamicEnumType);
}
static IEnumerable<object> _GetAllValues(Type type)
{
var m = type.GetMethod("GetAllValues", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var enumerable = m.Invoke(null, null);
return (IEnumerable<object>)enumerable;
}
}
// comment for stackoverflow: Now a class implementing it, note there is no more "#if NET80..." line here:
[DebuggerDisplay("{" + nameof(DisplayName) + ",nq}")]
public class eConfig : IDynamicEnum<eConfig>
{
public static eConfig Debug { get; } = new eConfig("Debug");
public static eConfig Release { get; } = new eConfig("Release");
public string DisplayName { get; }
/// <summary> Name of platform lowercased no duplicate space, trimmed </summary>
public string NormalizedName { get; }
/// <summary>
/// Open constructor to add custom / weird config as static public property in a child class.
/// </summary>
protected eConfig(string displayName, string normalizedName = null)
{
DisplayName = displayName;
NormalizedName = Normalize(normalizedName ?? displayName);
_AllStandardConfigurations = _AllStandardConfigurations ?? new();
_byNormalizedNames = _byNormalizedNames ?? new();
if (null == TryGetByNormalizedName(NormalizedName))
{
_byNormalizedNames.Add(NormalizedName, this);
_AllStandardConfigurations.Add(this);
}
}
public override string ToString()
{
return DisplayName;
}
public static IReadOnlyCollection<eConfig> AllStandardConfigurations => _AllStandardConfigurations;
static List<eConfig> _AllStandardConfigurations;
static Dictionary<string, eConfig> _byNormalizedNames;
public static eConfig GetByNormalizedName(string name)
{
return TryGetByNormalizedName(name)
?? throw new TechnicalException($"{name} not recognized as a valid configuration (or not yet handled, use constructor for that!)");
}
public static eConfig TryGetByNormalizedName(string name)
{
if (_byNormalizedNames.TryGetValue(Normalize(name), out var result))
return result;
return null;
}
static string Normalize(string name)
{
while (name.Contains(" "))
name = name.Replace(" ", " ");
return name.Trim().ToLowerInvariant();
}
public static IEnumerable<eConfig> GetAllValues() => _byNormalizedNames.Values;
public static ulong? MaxValueRepresentation => null;
string IDynamicEnum.NormalizedName => NormalizedName;
ulong IDynamicEnum.Value => throw new NotSupportedException();
}
要回答最初的问题(标题和示例代码中的注释中的问题),您只需从通用接口调用 EIMI 中带有
T.GetAllValues();
的非通用方法即可。
void Main() {
Test<Implementation>();
}
public void Test<T>()
where T : IDynamicEnum {
var result = T.GetAllValues();
Console.WriteLine(result.Count()); // 1
}
public class Implementation : IDynamicEnum<Implementation> {
public static IEnumerable<Implementation> GetAllValues() {
return new List<Implementation>(){
new Implementation()
};
}
}
public interface IDynamicEnum {
static abstract IEnumerable<object> GetAllValues();
}
public interface IDynamicEnum<out T> : IDynamicEnum
where T : class, IDynamicEnum<T> {
static new abstract IEnumerable<T> GetAllValues();
static IEnumerable<object> IDynamicEnum.GetAllValues() {
Console.WriteLine("EIMI Called");
return T.GetAllValues();
}
}