tags:

views:

327

answers:

4

I have a situation where I have a method that takes a couple of overloads. I have a fairly common scenario where if some condition is true, I call one of the overloads, otherwise I call something else. I decided to try to be clever and refactor the common code into a single generic method that would take an object and a condition, and call the overloaded method or otherwise call the common code.

A much simplified example of the code:

/// <summary>
/// Dummy interface
/// </summary>
public interface ITest1
{ }
/// <summary>
/// Dummy interface
/// </summary>
public interface ITest2
{ }
/// <summary>
/// Generic Class
/// </summary>
public class GenericClass
{
    /// <summary>
    /// First overload
    /// </summary>
    /// <param name="test1"></param>
    public void TestMethod(ITest1 test1)
    { }
    /// <summary>
    /// Second overload
    /// </summary>
    /// <param name="test2"></param>
    public void TestMethod(ITest2 test2)
    { }

    /// <summary>
    /// method with common logic
    /// </summary>
    /// <typeparam name="TInterfaceType">
    /// Type of the test object
    /// </typeparam>
    /// <param name="test">
    /// Test object to pass to the method.
    /// </param>
    public void ConditionallyCallTest<TInterfaceType>(
          TInterfaceType test, bool someLogic)
    {
     if (someLogic)
     {
      this.TestMethod(test);
     }
     else
     {
      // .. Perform Common operations here
     }
    }
}

Ignoring the fact that this doesn't do anything, if you compile this code segment, you will get a compiler error that it can't convert TInterfaceType to ITest1.

I was hoping for the compiler to wait until I specified the type to do the type checking, so:

GenericClass g = new GenericClass();
// We have an overload, so this is OK:
g.ConditionallyCallTest<ITest1>(test1);
// We have an overload, so this is OK:
g.ConditionallyCallTest<ITest2>(test2);
// Compiler error, no overload available:
g.ConditionallyCallTest<UnknownType>(obj);

Is it possible to do something like this using C#?

I also tried to use the where clause to specify the allowable types, but I couldn't figure out how to get the where clause to specify an either/or relationship between the specified types, only an AND relationship.

EDIT

As I mentioned in the comments below, I was attempting to avoid having to create matching overload methods, so are there any other suggestions to solve this problem, or am I limited by the language here?

+1  A: 

The compiler has to be able to compile your code without knowing what you're going to pass it. And by "your code" I mean for this case, the class that contains the methods.

In other words, it has to know that the call to TestMethod is legal, based on the things it knows when it compiles the method that does the call.

In this case, it knows nothing about TInterfaceType so it cannot guarantee that this is OK, and hence it complains.

You can, although it won't solve any problem at all, tell the compiler that the TInterfaceType generic type has to implement one or more interfaces, but you cannot tell it that the type has to implement one or another interface.

In other words, no, you cannot do this without some casting.


One way to do this would be to create multiple overloads, one for each interface type you want to support.

A generic type is basically for "any type possible", where you have a limited set of types that are allowed.

Using generics, you cannot do this without an explicit check to see if the type implements one or the other interface, a cast, and thus calling the explicit method directly.

You can't overload the method either, and differ it by generic constraints, as a method signature cannot differ by the generic constraint alone.

Lasse V. Karlsen
I guess hoping doesn't make it so... :) I expanded the question to see if there is a way to do something like this.
Brian B.
No, there isn't. The compiler is not going to create an infinte supply of overloaded classes/methods for you in the hope that someday you're going to call it with a "ConditionallyTest<XYZSomething>" and try to figure out how to deal with it. I meant it when I said you can't do this without a cast.
Lasse V. Karlsen
And "to see if there is a way to do something like this...". Yes, there is. You have to check for interface support, and cast appropriately. As I said, not doable without a cast. When the compiler is fighting you like this, stop and question whether this is the right way to go about it.
Lasse V. Karlsen
+2  A: 

You do even need a generic method? Just use plain overloading.

/// <summary>
/// Dummy interface
/// </summary>
public interface ITest1
{ }
/// <summary>
/// Dummy interface
/// </summary>
public interface ITest2
{ }
/// <summary>
/// Generic Class
/// </summary>
public class GenericClass
{
    /// <summary>
    /// First overload
    /// </summary>
    /// <param name="test1"></param>
    public void TestMethod(ITest1 test1)
    { }
    /// <summary>
    /// Second overload
    /// </summary>
    /// <param name="test2"></param>
    public void TestMethod(ITest2 test2)
    { }

    public void ConditionallyCallTest(ITest1 test, bool someLogic)
    {
        if(Common(someLogic))
            return;

        TestMethod(test);
    }

    public void ConditionallyCallTest(ITest2 test, bool someLogic)
    {
        if(Common(someLogic))
            return;

        TestMethod(test);
    }

    private bool Common(bool someLogic)
    {
        if (someLogic)
        {
            return false;
        }

        // .. Perform Common operations here
        return true;
    }
}
strager
Valid answer, and close to the solution I actually have, but I was hoping for something that would avoid having to create a corresponding method for each of my overloads. I went with generics because it seemed a clean and easy way to get it, which sparked my curiosity when it failed.
Brian B.
+2  A: 

Abstract out a common interface for all your types and just use polymorphism for this.

FlySwat
This is the correct answer. TestMethod should be a polymorphic method belonging to TInterfaceType.
mquander
A: 

I'm not sure what IType1 and IType2 are, but you obviously need an Interface IHasTestMethod that they inherit from

    interface IType0 { void Foo(); }
    interface IType1 : IType0 { }
    interface IType2 : IType0 { }

    void ConditionalCall(IType0 type, bool cond) 
    {
        if (Conditional(cond))
            type.Foo();
        return;
    }
Jimmy