重载泛型方法

本文关键字:泛型方法 重载 | 更新日期: 2023-09-27 18:04:47

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

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,现在似乎只有选项才能真正解决这个问题-但似乎像用大锤攻击苍蝇。

重载泛型方法

你可以这样做:

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);
    }
}

基本上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表达式在执行运行时类型检查时非常方便。它的工作原理类似于下面的代码:

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

EDIT:对于未知类型,通常使用以下模式:

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's,无论您是否将SpecializedClass对象传递给接口:

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方法,你会很好。

由于涉及泛型的函数和操作符重载是在编译时而不是运行时绑定的,因此if代码有两个方法:

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

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

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

参见以下内容:

    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();
        }
    }