【问题标题】:Unify the implementations of a generic interface统一通用接口的实现
【发布时间】:2019-12-20 01:59:05
【问题描述】:

我正在尝试找到一种方法来对实现通用接口的类型集合进行分组。

这是我的测试设置

using System;
using System.Collections.Generic;


namespace Example
{
    public interface ITypeA { }
    public interface ITypeB { }

    public class DescendantA1 : ITypeA { }
    public class DescendantA2 : ITypeA { }

    public class DescendantB1 : ITypeB { }
    public class DescendantB2 : ITypeB { }

    public interface ITypesMapper<in TTypeA, out TTypeB>
        where TTypeA : ITypeA
        where TTypeB : ITypeB
    {
        TTypeB Map(TTypeA typeA);
    }

    public class FirstMapper : ITypesMapper<DescendantA1, DescendantB1>
    {
        public DescendantB1 Map(DescendantA1 typeA) => throw new NotImplementedException();
    }

    public class SecondMapper : ITypesMapper<DescendantA2, DescendantB2>
    {
        public DescendantB2 Map(DescendantA2 typeA) => throw new NotImplementedException();
    }

    public class Program
    {
        static void Main(string[] args)
        {
            List<ITypesMapper<ITypeA, ITypeB>> types = new List<ITypesMapper<ITypeA, ITypeB>>();
            types.Add(new FirstMapper());
            types.Add(new SecondMapper());
        }
    }
}

上述示例的问题是我无法将FirstMapperSecondMapper 添加到集合中,因为我没有捕获它们的接口。 (C# 编译器说它不能将FirstMapper and SecondMapper 转换为ITypesMapper&lt;TTypeA, TTypeB&gt;

我认为通过将我的接口的通用参数限制为继承 ITypeAITypeB 接口的类型,并使它们成为协/逆变我可以实现这一点。


我可以解决此问题的一种方法是将我的 Mapper 类更改为继承 ITypesMapper&lt;ITypeA, ITypeB&gt;,但随后我会在其 Map() 方法中丢失强类型对象,我将不得不投掷。我不想要这个。

另一种方法是定义一个IMapper 接口,该接口将由ITypesMapper&lt;,,&gt; 继承,但是我无法定义Map() 方法来处理泛型参数。


我想知道是否有办法创建一组 Mapper 类(如 FirstMapperSecondMapper),每个类都必须有一个可以使用泛型参数的 Map 方法并且有办法将所有这些统一在同一个界面下,然后我可以调用。

我可以更改设计中的所有内容,我只需要:

  • 一个界面,我可以将它们全部放在后面(以便它们可以放在一个集合中)。
  • 接口应该公开ITypeB Map(ITypeA input)方法
  • 具体的映射器实现应该使用具体的类型(所以映射器应该有DescendantB1 Map(DescendantA1 typeA)而不是ITypeB Map(ITypeA input)

【问题讨论】:

  • IEnumerable&lt;String&gt; 可以转换为IEnumerable&lt;Object&gt;,因为从它出来的任何字符串都是一个对象。 IList&lt;String&gt; 不能转换为 IList&lt;Object&gt;,因为任何 添加 的对象都可能不是字符串。 IEnumerable&lt;T&gt; 的类型参数是 out 参数。您的TTypeA 参数是in,这会给您带来麻烦。如果您可以将其更改为out,那就太好了。但你不能。剩下的就是计划 B:一个非通用的“基本”接口ITypesMapper,类似于System.Collections.IList
  • @Kobek 基接口可以为空,或者子接口可以显式实现。当然,您的通用 Map() 原型是整个练习的重点,放弃它几乎没有意义。但是System.Collections.Generic.List&lt;T&gt;implements System.Collections.IList explicitly.
  • @Kobek 显式实现它让你有一个 explicit 非泛型 ITypeB ITypesMapper.Map(ITypeA typeA); 方法,它满足显式实现的非泛型接口 但不与 通用public TTypeB Map(ITypeA TypeA);。就像 List&lt;T&gt; 在链接的参考源中所做的那样:int System.Collections.IList.Add(Object item)public int Add(T item)。但是我们仍然有一个问题,当您从集合中检索这些东西时,您将如何使用它们。
  • @Kobek 存储然后是微不足道的,如图所示。来说说使用吧。请提供有关您计划如何使用它们的更多详细信息。在我看来,在使用时,所有具体类型都必须在编译时知道。对吗?
  • 所以你的映射器在编译时接受一个具体类型未知的ITypeA,并在编译时给你一个具体类型未知的ITypeB?泛型可以是答案的一部分,但在这一点上,要么你必须能够将 ITypeB 转换为编译时已知的某个具体类型,要么你必须能够做你需要做的一切通过非泛型 OOP 来处理它:虚拟、接口等。泛型非常不适合像这样的动态东西。当然,在类的具体实现中随意使用它们。

标签: c# oop inheritance types interface


【解决方案1】:

这是我认为你能得到的最接近的(这个解决方案也展示了你正在尝试做的问题)-

public interface ITypeA { }
public interface ITypeB { }

public class DescendantA1 : ITypeA { }
public class DescendantA2 : ITypeA { }

public class DescendantB1 : ITypeB { }
public class DescendantB2 : ITypeB { }

public interface ITypeMapper<in TTypeA, out TTypeB>
{
    TTypeB Map(TTypeA typeA);
}

public abstract class TypesMapper<TTypeA, TTypeB>: ITypeMapper<ITypeA, ITypeB>
    where TTypeA : ITypeA
    where TTypeB : ITypeB 
{
    public ITypeB Map(ITypeA typeA) => internalMap(typeA);

    protected abstract TTypeB internalMap(TTypeA a);
}

public class FirstMapper : TypesMapper<DescendantA1, DescendantB1>
{
    protected override DescendantB1 internalMap(DescendantA1 typeA) => throw new NotImplementedException();
}

public class SecondMapper : TypesMapper<DescendantA2, DescendantB2>
{
    protected override DescendantB2 internalMap(DescendantA2 typeA) => throw new NotImplementedException();
}

public static class ProgramTest
{
    static void Main(string[] args)
    {
        List<ITypeMapper<ITypeA, ITypeB>> types = new List<ITypeMapper<ITypeA, ITypeB>>();
        types.Add(new FirstMapper());
        types.Add(new SecondMapper());
    }
}

您在此处遇到的错误在以下行中:

public ITypeB Map(ITypeA typeA) => internalMap(typeA);

告诉我们不能将 ITypeA(基础)转换为 TTypeA(具体)。

现在,我们可以在通用抽象类定义中添加一个类约束,即。

where TTypeA : class, ITypeA

并转换给出错误的行:

public ITypeB Map(ITypeA typeA) => internalMap(typeA as TTypeA);

但是没有什么可以阻止这个列表的使用者使用不是 TTypeA 的参数调用 Map(...),这就是为什么整个方案一开始就不是类型安全的。尽管如此,如果您正在使用某种检查,并且您真的只会使用正确的具体类型调用 Map 函数,我认为这会让您到达您想去的地方。

【讨论】:

  • 感谢您的回复。我继续做了一件类似的事情——通过使用@EdPlunkett 的建议,并明确地实现了基本映射器,然后将其称为我的泛型类型。所以几乎和你提供的答案一样。至于它不安全——是的,我知道这一点。我确实有一种机制可以保证我用正确的类型调用它。唯一可能发生的问题是在未来 - 其他人可以很容易地滥用代码。
猜你喜欢
  • 2022-11-28
  • 1970-01-01
  • 1970-01-01
  • 2022-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多