Scott Langham
Scott Langham

Reputation: 60351

How to conditionally cast to multiple types in c#

I'm looking at a function with this pattern:

if( obj is SpecificClass1 )
{
   ((SpecificClass1)obj).SomeMethod1();
}
else if( obj is SpecificClass2 )
{
   ((SpecificClass2)obj).SomeMethod2();
}
else if( obj is SpecificClass3 )
{
   ((SpecificClass3)obj).SomeMethod3();
}

and get a code analysis warning: CA1800 Do not cast unnecessarily.

What's a good code pattern I can use to replace this code with that will be performant and concise.

Update

I didn't say, but obj is declared with type object.

I originally asked two questions here. I've split one off (which nobody had yet answered anyway): Why wouldn't the compiler optimize these two casts into one?

Upvotes: 5

Views: 8476

Answers (8)

Fugal Junk
Fugal Junk

Reputation: 63

Not sure if I'm missing a goal, but here's an option that should work.

if( obj is SpecificClass1 sc1 )
{
   sc1.SomeMethod1();
}
else if( obj is SpecificClass2 sc2 )
{
   sc2.SomeMethod2();
}
else if( obj is SpecificClass3 sc3 )
{
   sc3.SomeMethod3();
}
else
{
   throw new exception();
}

You can also

switch (obj)
{
    case SpecificClass1 sc1:
        sc1.SomeMethod1();
        break;
    case SpecificClass2 sc1:
        sc2.SomeMethod2();
        break;
    case SpecificClass3 sc1:
        sc3.SomeMethod3();
        break;
    default:
        throw new Exception();
}

Upvotes: 1

Scott Langham
Scott Langham

Reputation: 60351

How about writing a method

public static class ObjectExtensions
{
    public static bool TryCast<T>(this object from, out T to) where T : class
    {
        to = from as T;
        return to != null;
    }
}

and using it:

SpecificClass1 sc1;
SpecificClass2 sc2;
SpecificClass3 sc3;

if( obj.TryCast(out sc1) )
{
    sc1.SomeMethod1();
}
else if( obj.TryCast(out sc2) )
{
    sc2.SomeMethod2();
}
else if( obj.TryCast(out sc3) )
{
    sc3.SomeMethod3();
}

Upvotes: 0

MattC
MattC

Reputation: 4004

Ok, bit rough, but:

    public class BaseClass{}

    public class SubClass1 : BaseClass  
    {
        public void SomeMethod1()
        {
        }
    }

    public class SubClass2 : BaseClass  
    {
        public void SomeMethod2()
        {
        }
    }

    public class Class1
    {
        public Class1()
        {
            var test = new SubClass1();

            var lookup = new Dictionary<Type, Action<object>>
                             {
                                 {typeof (SubClass1), o => ((SubClass1) o).SomeMethod1() },
                                 {typeof (SubClass2), o => ((SubClass2) o).SomeMethod2() }
                             };

            //probably best to check the type exists in the dictionary first, 
            //maybe wrap up the execution into a class of it's own so it's abstracted away
            lookup[test.GetType()](test);

        }
    }

Upvotes: 1

Joachim Isaksson
Joachim Isaksson

Reputation: 180887

The most extensible solution would probably be inheriting the concrete class while implementing an interface with a SomeMethod implementation that calls the correct SomeMethodx method on the inherited class. That way, you'll keep the existing interface while still keeping the existing methods.

public interface ISomething {
    void SomeMethod();
}

public SpecificClass1Wrapper : SpecificClass1, ISomething {
    void SomeMethod() { SomeMethod1(); }
}

If the objects are wrapped in this way before they're stored in the object reference, a cast to ISomething and a call to SomeMethod() will replace your entire if/else combination.

If the object on the other hand comes from code you have no way of extending and terse but still clear is what you're going for, you could create a simple helper method;

private bool CallIfType<T>(object obj, Action<T> action) where T : class
{
    var concrete = obj as T;

    if (concrete == null)
        return false;

    action(concrete);
    return true;
}

You can then write the calls as a simple expression;

var tmp = CallIfType<SpecificClass1>(obj, x => x.SomeMethod1()) ||
          CallIfType<SpecificClass2>(obj, x => x.SomeMethod2()) ||
          CallIfType<SpecificClass3>(obj, x => x.SomeMethod3());

if(tmp)
    Console.WriteLine("One of the methods was called");

Upvotes: 1

Markus
Markus

Reputation: 22446

Interface

The best way would be to introduce an interface that all the types implement. This is only possible if the signatures match (or you don't have too many differences).

Using as

If creating an interface is not an option, you can get rid of the CA message by using the following pattern (though this also introduces unnecessary casts and therefore degrades performance a bit):

var specClass1 = obj as SpecificClass1;
var specClass2 = obj as SpecificClass2;
var specClass3 = obj as SpecificClass3;
if(specClass1 != null)
   specClass1.SomeMethod1();
else if(specClass2 != null)
   specClass2.SomeMethod2();
else if(specClass3 != null)
   specClass3.SomeMethod3();

You can also change it to this structure (from my point of view, the above is better in terms of readability):

var specClass1 = obj as SpecificClass1;
if (specClass1 != null)
   specClass1.SomeMethod1();
else
{
    var specClass2 = obj as SpecificClass2;
    if (specClass2 != null)
        specClass2.SomeMethod2();
    else
    {
        var specClass3 = obj as SpecificClass3;
        if (specClass3 != null)
            specClass3.SomeMethod3();
    }
}

Registering the types in a dictionary

Also, if you have many types that you want to check for, you can register them in a dictionary and check against the entries of the dictionary:

var methodRegistrations = new Dictionary<Type, Action<object> act>();
methodRegistrations.Add(typeof(SpecificClass1), x => ((SpecificClass1)x).SomeMethod1());
methodRegistrations.Add(typeof(SpecificClass2), x => ((SpecificClass2)x).SomeMethod2());
methodRegistrations.Add(typeof(SpecificClass3), x => ((SpecificClass3)x).SomeMethod3());

var registrationKey = (from x in methodRegistrations.Keys 
                       where x.IsAssignableFrom(obj.GetType()).FirstOrDefault();
if (registrationKey != null)
{
    var act = methodRegistrations[registrationKey];
    act(obj);
}

Please note that the registrations are easily extendable and that you can also call methods with different arguments in the action.

Upvotes: 3

Only a Curious Mind
Only a Curious Mind

Reputation: 2857

Your classes can be inherite a ISomeMethodInterface like this:

  public interface ISomeMethodInterface
{ 
     void SomeMethod();
}

public class SpecificClass1 : ISomeMethodInterface
{ 
 //some code
    public void SomeMethod()
    {

    }
}

public class SpecificClass2 : ISomeMethodInterface
{ 
 //some code
    public void SomeMethod()
    {

    }
}

public class SpecificClass3 : ISomeMethodInterface
{ 
 //some code
    public void SomeMethod()
    {

    }
}

And in your call:

((ISomeMethodsInterface)obj).SomeMethod();

Upvotes: 1

danish
danish

Reputation: 5600

Can you do something like this here?

interface IBaseInterface
{
    void SomeMethod();
}

public class Implementer1:IBaseInterface
{

    public void SomeMethod()
    {
        throw new NotImplementedException();
    }
}

public class Implementer2 : IBaseInterface
{

    public void SomeMethod()
    {
        throw new NotImplementedException();
    }
}

public class Implementer3 : IBaseInterface
{

    public void SomeMethod()
    {
        throw new NotImplementedException();
    }
}

And then, in caller code:

IBaseInterface concrete = GetInstance();
            concrete.SomeMethod();

and GetInstance would create class objects based on conditions.

Upvotes: 1

Luis Filipe
Luis Filipe

Reputation: 8708

To avoid the double casting you could do the following

var objClass1= obj as SpecificClass1;
if(objClass1!=null)
   objClass1.SomeMethod1();

Regarding the pattern you could make all these classes implement a common interface and make your method receive the interface.

public void SomeMethod(ISpecificInterface specific)
{
  specific.SomeMethod1();
}

Upvotes: 2

Related Questions