【问题标题】:Overloading generic methods重载泛型方法
【发布时间】:2013-03-09 15:27:57
【问题描述】:

在调用通用方法来存储对象时,有时需要以不同方式处理特定类型。我知道您不能基于约束进行重载,但任何其他替代方案似乎都存在其自身的问题。

public bool Save<T>(T entity) where T : class
{ ... some storage logic ... }

我想做的事情如下:

public bool Save<SpecificClass>(T entity)
{ ... special logic ... }

过去,我们的团队创建了“一次性”方法来保存这些类,如下所示:

public bool SaveSpecificClass(SpecificClass sc)
{ ... special logic ... }

但是,如果您不知道该功能的存在,并且您尝试使用通用(保存),那么您可能会遇到许多本应“一次性”解决的问题。如果新的开发人员出现,发现泛型存在问题,并决定使用自己的一次性功能来解决问题,情况可能会变得更糟。

所以...

有哪些选项可以解决这个看似常见的问题?

我已经查看并使用了 UnitOfWork,现在这似乎是 唯一 真正解决问题的选项 - 但似乎用大锤攻击苍蝇。

【问题讨论】:

  • 与 C++ 不同,C# 不允许模板特化
  • 这些 Save() 方法是放在一些轻量级的帮助类中还是放在一些实体类中?我只是在考虑继承,但确保这是正确的方式很重要,因为继承并不总是正确使用

标签: c# generics overloading


【解决方案1】:

你可以这样做:

public bool Save<T>(T entity) where T : class
{ ... some storage logic ... }

public bool Save(SpecificClass entity)
{ ... special logic ... }

例如:

public class SpecificClass
{
}

public class Specializer
{
    public bool GenericCalled;
    public bool SpecializedCalled;

    public bool Save<T>(T entity) where T : class
    {
        GenericCalled = true;
        return true;
    }

    public bool Save(SpecificClass entity)
    {
        SpecializedCalled = true;
        return true;
    }
}

public class Tests
{
    [Test]
    public void TestSpecialization()
    {
        var x = new Specializer();
        x.Save(new SpecificClass());
        Assert.IsTrue(x.SpecializedCalled);
        Assert.IsFalse(x.GenericCalled);
    }
}

【讨论】:

  • 我无法让它工作:无论如何都调用了该函数的通用版本。
  • @MarkusParker 你能提供一个例子,因为这应该可以正常工作,正如我的更新所详述的那样,它显示它在单元测试中工作。请记住,用于传递 SpecificClass 的变量必须是 SpecificClass 类型,除非它是 dynamic 类,因为 C# 执行编译时多态性。
  • 类测试 { public bool GenericCalled; public bool SpecializedCalled; public bool Save(T entity) { GenericCalled = true;返回真; } public bool Save(int entity) { SpecializedCalled = true;返回真; } public void HandleGenericType() { 保存(default(T)); } } 这里 HandleGenericType() 调用通用版本。
  • @markusParker 啊,好的。这就是编译时的多态性。在编译时,HandleGenericType 不知道可以调用int 并专门调用Save(int),因此它链接到通用Save&lt;T&gt;。您可以通过将default&lt;T&gt; 转换为dynamic 来使其执行运行时多态性。例如:保存((动态)默认(T));`
  • @PeterRitchie - 非常感谢!!我已经在谷歌上搜索了 20 分钟,试图找到答案,就是这样!
【解决方案2】:

嗯,基本上 C# 不允许模板特化,除非通过像这样的继承:

interface IFoo<T> { }
class Bar { }

class FooBar : IFoo<Bar> { }

至少它在编译时不支持这个。但是,您可以使用 RTTI 来完成您想要实现的目标:

public bool Save<T>(T entity)
{
    // Check if "entity" is of type "SpecificClass"
    if (entity is SpecificClass)
    {
        // Entity can be safely casted to "SpecificClass"
        return SaveSpecificClass((SpecificClass)entity);
    }

    // ... other cases ...
}

is expression 非常方便进行运行时类型检查。它的工作原理类似于以下代码:

if (entity.GetType() == typeof(SpecificClass))
    // ...

编辑:未知类型使用以下模式很常见:

if (entity is Foo)
    return DoSomethingWithFoo((Foo)entity);
else if (entity is Bar)
    return DoSomethingWithBar((Bar)entity);
else
    throw new NotSupportedException(
        String.Format("\"{0}\" is not a supported type for this method.", entity.GetType()));

EDIT 2 :正如其他答案所建议的那样重载使用SpecializedClass 的方法,如果您使用多态性,您需要小心。如果您正在为存储库使用接口(这实际上是设计存储库模式的一种好方法),那么在某些情况下,无论您是否传递 @ 对象,重载都会导致您调用错误的方法 get 的情况987654328@到接口:

interface IRepository
{
    bool Save<T>(T entity)
        where T : class;
}

class FooRepository : IRepository
{
    bool Save<T>(T entity)
    {
    }

    bool Save(Foo entity)
    {
    }
}

如果您使用Foo 的实例直接调用FooRepository.Save,则此方法有效:

var repository = new FooRepository();
repository.Save(new Foo());

但是,如果您正在调用接口(例如,如果您使用模式来实现存储库创建),这将不起作用:

IRepository repository = GetRepository<FooRepository>();
repository.Save(new Foo());  // Attention! Call's FooRepository.Save<Foo>(Foo entity) instead of FooRepository.Save(Foo entity)!

使用 RTTI,只有一个 Save 方法,你会没事的。

【讨论】:

  • 采用Save(SpecificClass entity)等特定类型的重载有什么问题
  • 没有什么问题,但它仍然是超载,而不是专业化。 ;)
  • 问题关于“重载”... :)
  • 没有人说过不同的东西...我只是在回答 OP 的评论“但是,如果您不知道该功能存在,并且您尝试使用通用(保存),那么您可以运行进入“一次性”应该解决的许多问题。如果新开发人员出现,看到泛型的问题,并决定用自己的一次性功能解决它,情况可能会变得更糟."。它也有效。它只是已经提供的答案的替代方案。无需投票...
  • 另外我更喜欢使用 RTTI,因为它使开发人员更容易正确地做(只有一种方法可以调用),而不是做错(可能调用错误的方法)。还要看看问题 cmets,因为专业化是 OP 所做的(无论他是否要求“重载”)。
【解决方案3】:

因为涉及泛型的函数和运算符重载是在编译时而非运行时绑定的,如果代码有两种方法:

public bool Save<T>(T entity) ...
public bool Save(SomeClass entity) ...

然后尝试调用Save(Foo)(其中Foo 是某个泛型类型的变量)的代码将始终调用前一个重载,即使泛型类型恰好是SomeClass。我的建议是用非泛型方法DoSave(T param) 定义泛型接口ISaver&lt;in T&gt;。让提供Save 方法的类为它可以处理的类型实现所有适当的泛型接口。然后让对象的Save&lt;T&gt; 方法尝试将this 转换为ISaver&lt;T&gt;。如果转换成功,则使用生成的ISaver&lt;T&gt;;否则执行通用保存。如果类类型声明列出了它可以保存的类型的所有适当接口,这种方法会将Save 调用分派给适当的方法。

【讨论】:

    【解决方案4】:

    为什么要为您的方法使用不同的名称?

    请参阅以下内容:

        public class Entity
        {
        }
    
        public class SpecificEntity : Entity
        {
        }
    
        public class Program
        {
            public static void Save<T>(T entity)
                where T : class
            {
                Console.WriteLine(entity.GetType().FullName);
            }
    
            public static void Save(SpecificEntity entity)
            {
                Console.WriteLine(entity.GetType().FullName);
            }
    
            private static void Main(string[] args)
            {
                Save(new Entity());          // ConsoleApplication13.Entity
                Save(new SpecificEntity());  // ConsoleApplication13.SpecificEntity
    
                Console.ReadKey();
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-16
      • 1970-01-01
      • 1970-01-01
      • 2015-02-26
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      相关资源
      最近更新 更多