是否可以从另一个静态接口方法的实现中调用静态抽象接口方法?

问题描述 投票:0回答:1

问题就在下面代码的注释中 顺便说一句,此代码仅对 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 文件相关的所有微小值集:

  • 配置(“调试”,“发布”,用户定义的自定义配置...无法提前知道它们)
  • 平台(“x86”、x64”、“Any Cpu”,后面有很多不同的写法,有空格,有“CPU”或“cpu”)
  • 框架:包含很多不同的值。标准的(我想要处理的):“net48”,...,“net8.0”,但也有很多我不想处理的奇特/稀有值,例如“net7.0-tvos” ”。他们还有很多写作可能(带点或不带点......)

因此,要拥有“正确的”算法,最好有一种方法可以简单地识别它们,而无需每次都处理它们的所有书写形式。实际上,这更像是一种 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();
    }
c# static-methods abstract c#-11.0 default-interface-member
1个回答
0
投票

要回答最初的问题(标题和示例代码中的注释中的问题),您只需从通用接口调用 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();
    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.