23

When calling a generic method for storing an object there are occasionally needs to handle a specific type differently. I know that you can't overload based on constraints, but any other alternative seems to present its own problems.

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

What I would LIKE to do is something like the following:

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

In the past our team has created 'one-off' methods for saving these classes as follows:

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

However, if you don't KNOW that function exists, and you try to use the generic (Save) then you may run into a host of problems that the 'one-off' was supposed to fix. This can be made worse if a new developer comes along, sees the problem with the generic, and decides he's going to fix it with his own one-off function.

So...

What are the options for working around this seemingly common issue?

I've looked at, and used UnitOfWork and right now that seems to be the only option that actually resolves the problem - but seems like attacking a fly with a sledgehammer.

Patrick Dench
  • 813
  • 2
  • 9
  • 27
  • 3
    Unlike C++, C# doesn't allow template specialization – Panagiotis Kanavos Mar 20 '13 at 13:40
  • Are these Save() methods placed in some lightweight helper class or in some entity classes? I'm just thinking about inheritance but it is important to be sure this is right way because inheritance not always used properly – sll Mar 20 '13 at 13:45
  • Possible duplicate [How to do template specialization in C#](http://stackoverflow.com/questions/600978/how-to-do-template-specialization-in-c-sharp) – Panagiotis Kanavos Mar 20 '13 at 13:52

4 Answers4

23

You could do :

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

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

For example:

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);
    }
}
Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • 3
    I could not get it to work: The generic version of the function was called in any case. – MarkusParker Jul 11 '17 at 10:03
  • @MarkusParker Can you provide an example as this should work fine, as detailed by my update that shows it working in a unit test. Keep in mind that the variable used to pass the `SpecificClass` must be of type `SpecificClass` unless it's a `dynamic` class as C# does compile-time polymorphism. – Peter Ritchie Jul 18 '17 at 18:23
  • class Test { public bool GenericCalled; public bool SpecializedCalled; public bool Save(T entity) { GenericCalled = true; return true; } public bool Save(int entity) { SpecializedCalled = true; return true; } public void HandleGenericType() { Save(default(T)); } } Here HandleGenericType() calls the generic version. – MarkusParker Jul 19 '17 at 11:23
  • 4
    @markusParker Ah, okay. That's the compile-time polymorphism kicking in. At compile time, `HandleGenericType` doesn't know that an `int` could be called and specifically call `Save(int)` so it links to the generic `Save`. You can get it to do the run-time polymorphism by casting `default` to `dynamic`. e.g.: Save((dynamic)default(T));` – Peter Ritchie Jul 20 '17 at 17:45
  • @PeterRitchie - thank you so much!! I've been googling for 20 minutes trying to find the answer, and this was it! – Iamsodarncool Apr 21 '18 at 01:53
  • A little gotcha is, that a class derived from `SpecificClass` will go call the generic Save and not the Specific Save method. – sschoof May 22 '19 at 14:21
  • @sschoof yep, use the `new` keyword to access the new Save through a variable of type of the new class. – Peter Ritchie May 24 '19 at 02:33
  • @PeterRitchie I do not understand how the `new` keyword helps. Having something like`class DerivedClass : SpecificClass` and run `x.Save(new DerivedClass())` calls the Generic Save method and not the Specialized. How can I fix this with an `new`? – sschoof May 28 '19 at 09:00
  • @sschoof Not sure what you're trying to accomplish to know what a "fix" is. If you're trying to get runtime dispatch, (i.e. calling a type's method based on what the argument is at _run-time_, not compile-time) then this won't do that for you. But, C# doesn't do that sort of thing just through the type system. – Peter Ritchie May 28 '19 at 21:22
7

Well basicly C# does not allow template specialization, except through inheritence like this:

interface IFoo<T> { }
class Bar { }

class FooBar : IFoo<Bar> { }

At least it does not support this during compile time. However you can use RTTI to do what you are trying to achieve:

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 ...
}

The is expression is pretty handy to do runtime type checks. It works similar to the following code:

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

EDIT : It is pretty common for unknown types to use the following pattern:

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 : As the other answers suggest overloading the method with the SpecializedClass you need to take care if you are working with polymorphism. If you are using interfaces for your repository (which is actually a good way to design the repository pattern) there are cases where overloading would lead to cases in which you are the wrong method get's called, no matter if you are passing an object of SpecializedClass to the interface:

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

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

    bool Save(Foo entity)
    {
    }
}

This works if you directly call FooRepository.Save with an instance of Foo:

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

But this does not work if you are calling the interface (e.g. if you are using patterns to implement repository creation):

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

Using RTTI there's only one Save method and you'll be fine.

Carsten
  • 11,287
  • 7
  • 39
  • 62
  • What's wrong with an overload that takes a specific type like `Save(SpecificClass entity)` – Peter Ritchie Mar 20 '13 at 14:26
  • 1
    Nothing is wrong with it, but it's still an overload, not a specialization. ;) – Carsten Mar 20 '13 at 14:32
  • Question *is* about "overloading"... :) – Peter Ritchie Mar 20 '13 at 14:38
  • Nobody has told something different... I was just answering the OP's comment "However, if you don't KNOW that function exists, and you try to use the generic (Save) then you may run into a host of problems that the 'one-off' was supposed to fix. This can be made worse if a new developer comes along, sees the problem with the generic, and decides he's going to fix it with his own one-off function.". And it **works**, too. It's just an alternative to the already provided answers. No need to downvote it... – Carsten Mar 20 '13 at 14:42
  • Also I prefer using RTTI, because it makes it easier for the developer to do it right (There is only one method to call), instead of doing it wrong (perhaps calling the wrong method). Also take a look at the question comments, because specialization is what the OP does (no matter if he asks for "overloading"). – Carsten Mar 20 '13 at 14:45
  • It's not "template specialization" sure, but an overload with a specific argument that overloads a generic method seems like specialized behaviour to me. – Peter Ritchie Mar 20 '13 at 14:48
  • In fact my answer does not provide "template specialization", either, but the behaviour is also the same! So why downvoting it? – Carsten Mar 20 '13 at 14:54
  • btw... there is a difference in "template specialization" and "overloading" like you described it: imagine the interface `IFoo` defines a method `Save`. The specialization `CFoo` implements `IFoo` and provides an overload `Save(Specialized s)` like you described it. A client calls the interface `IFoo foo = GetFoo(); foo.Save(new Specialized());` which behaviour acts more like what the OP wants? RTTI or overloading?! Right... RTTI works, while overloading would still call `Save`. ;-) – Carsten Mar 20 '13 at 15:04
  • +1 Overloads may not always work, as @Aschratt pointed out. Another case they wouldn't work is if the `Save` method was called from another generic type with the same constraint. This is the likely the best way to consistently get the desired behavior in C#. – jam40jeff Mar 20 '13 at 21:58
  • There's lots of information floating around about re-designing conditionals on types to polymorphism. The overwhelming opinion is to prefer polymorphism. That's not to say this pattern should never be used; but the OP didn't include any of criteria to suggest this is a better solution. – Peter Ritchie Mar 22 '13 at 16:54
5

Because function and operator overloads involving generics are bound at compile-time rather than run-time, if code has two methods:

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

then code which tries to call Save(Foo) where Foo is a variable of some generic type will always call the former overload, even when the generic type happens to be SomeClass. My suggestion to resolve that would be to define a generic interface ISaver<in T> with a non-generic method DoSave(T param). Have the class that provides the Save method implement all of the appropriate generic interfaces for the types it can handle. Then have the object's Save<T> method try to cast this to an ISaver<T>. If the cast succeeds, use the resulting ISaver<T>; otherwise perform a generic save. Provided that the class type declaration lists all of the appropriate interfaces for the types it can save, this approach will dispatch Save calls to the proper methods.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

Why using different names for your method?

See the following:

    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();
        }
    }
ken2k
  • 48,145
  • 10
  • 116
  • 176