Return one of two possible objects of different types sharing a method
Asked Answered
T

6

18

I have 2 classes:

public class Articles
{
    private string name;

    public Articles(string name)
    {
        this.name = name;
    }

    public void Output()
    {
        Console.WriteLine("The class is: " + this.GetType());
        Console.WriteLine("The name is: " + name);
    }
}

And

public class Questionnaire 
{
    private string name;

    public Questionnaire(string name)
    {
        this.name = name;
    }

    public void Output()
    {
        Console.WriteLine("The class is: " + this.GetType());
        Console.WriteLine("The name is: " + name);
    }
}

I want to write a method, that takes an integer (1 meaning Articles should be returned, 2 meaning Questionnaire) and a name.

This method must return an instance of one of those two classes:

public [What type??] Choose(int x, string name)
    {
        if (x == 1)
        {
           Articles art = new Articles(name);
           return art;
        }
        if (x == 2)
        {
            Questionnaire ques = new Questionnaire(name);
            return ques;
        }
    }

What return type should I use, so I can call Output() on the result?

Telesthesia answered 5/7, 2013 at 14:20 Comment(2)
If you think that both types have (many) things in common you should let them both inherit from the same base class or at least implement the same interface.Aslam
This commonly called duck typing - definitely possible with C# (see dynamic), but consider using strongly typed solutions shown in answers.Hardandfast
I
27

Why not have a base class that has Output defined. Then return the base.

public abstract class BaseType {
    public abstract void Output();
}

Both Articles and Questionaire should inherit this BaseType.

public class Articles : BaseType {
  // Output method here
}

public class Questionaire : BaseType {
 // Output method here
}

Then you can do:

public static BaseType Choose(int x, string name) 
{
    if (x == 1)
    {
       Articles art = new Articles(name);
       return art;
    }
    if (x == 2)
    {
        Questionnaire ques = new Questionnaire(name);
        return ques;
    }
}

You could also achieve this via an interface.

public interface IInterface {
    void Output();
}

public class Articles : IInterface {
  // Output method here
}

public class Questionaire : IInterface {
 // Output method here
}

You would then have to modify the Choose method to return IInterface rather than BaseType. Whichever you choose is up to you.

Note: even if you can't change original classes you can still use these approaches before resorting to dynamic by providing wrapper classes that implement the interface and either inherits original or forwards calls to corresponding method:

public class ArticlesProxy : Articles, IInterface 
{
  public ArticlesProxy(string name) : base(name){}

}

public class QuestionaireProxy : Questionaire, IInterface {
  Questionaire inner;
  public QuestionaireProxy(string name) {  inner = new Questionaire(name); }

  public void Output() { inner.Output();}

}
Intoxication answered 5/7, 2013 at 14:22 Comment(3)
A good pratice is either use a base type, like an Printable class, per example or an interface like IPrintable.Dynast
Keeping in mind that I used that name so it relates to your classesDynast
This is more or less a variation on the Factory design pattern: oodesign.com/factory-pattern.htmlPennyworth
I
15

How about something like this:

public interface IHasOutput
{
    void Output();
}

public class Articles : IHasOutput

public class Questionnaire : IHasOutput

and then:

public static IHasOutput Choose...

You can of course call your interface anything you'd like, other than IHasOutput, I just don't know what to call it. This is what interfaces are for. Two different concrete implementations that share a common interface. Now when you call it you can do this:

var entity = MyClass.Choose(1, "MyName");
entity.Output();

and it doesn't matter what concrete implementation is returned. You know it implements a common interface.

Imhoff answered 5/7, 2013 at 14:23 Comment(1)
You should rename IEntity to IHasOutput, because it represent a very specific behavior and not a common descriptor of the whole class. But it's still a very valid answer.Reseau
A
7

Answers provided here are great but one thing I don't like is parameter x that chooses what type should be created. That creates use of magic number, which may become head-ache even for you later.

You can take advantage of generics here, i.e. make method Choose:

public static T Choose<T>(string name)
        // type constraint to ensure hierarchy.
        where T : BaseClass // BaseClass have common functionality of both class.
    {
        // Unfortunately you can't create instance with generic and pass arguments
        // to ctor. So you have to use Activator here.
        return (T)Activator.CreateInstance(typeof(T), new[] { name });
    }

Usage:

Articles article = ClassWithChooseMethod.Choose<Articles>("name");
Questionnaire questionnaire = ClassWithChooseMethod.Choose<Questionnaire>("name2");

Demo

Edit

As @OlivierJacot-Descombes mentioned in comment x that chooses type might be user-input. In that case you can create enum with respective values:

enum ArticleType {
    Articles = 1,
    Questionnaire = 2
}

And have overload of Choose:

public static BaseClass Choose(ArticleType type, string name) {
    switch (type) {
        case ArticleType.Articles:
            return ClassWithChooseMethod.Choose<Articles>(name);
        case ArticleType.Questionnaire:
            return ClassWithChooseMethod.Choose<Questionnaire>(name);
        default:
            return default(BaseClass);
    }
}

and usage:

var obj = ClassWithChooseMethod.Choose((ArticleType)userInput, "some name");

This gives you possibility to keep your code cleaner and useful for future maintenance (e.g. you can change logic of class creation in Choose).

P.S. You might be interested to read more about factory pattern.

Afrika answered 5/7, 2013 at 14:50 Comment(2)
And where do you get the type T from? Most likely the value of x is not hard-coded, but comes from a user input.Bib
@OlivierJacot-Descombes Maybe it is, maybe not. It was not mentioned in OP. And even if it is, I'd create enum to handle types and use switch outside of Choose method or I'd have its non-generic overload that would internally use generic method to return respective type.Afrika
T
6

Unless they share the same base class or interface, you're pretty much stuck with either object or dynamic.

Tuxedo answered 5/7, 2013 at 14:22 Comment(2)
This is really never neccessary - that's just bad code designTammara
I was not suggesting it was good design. Only that with the design as stated, those are the options.Tuxedo
B
1

The most flexible way to solve this problem is to write an interface as well as an abstract base class implementing it. This way you have the freedom to derive a class from the base class or to implement the interface directly, if the base class does not satisfy your needs in a very special case or if a class derives from another class already. Also make the method Output virtual; this enables you to override it if necessary. Also make the name protected; this enables you to use it in derived classes

public interface IHasOutput
{
    void Output();
}

public abstract class OutputBase : IHasOutput
{
    protected string _name;

    public OutputBase(string name)
    {
        _name = name;
    }

    #region IHasOutput Members

    public virtual void Output()
    {
        Console.WriteLine("The class is: " + this.GetType());
        Console.WriteLine("The name is: " + _name);
    }

    #endregion

    public static IHasOutput Choose(int x, string name)
    {
        switch (x) {
            case 1:
                return new Articles(name);
            case 2:
                return new Questionnaire(name);
            default:
                return null;
        }
    }
}

public class Articles : OutputBase
{
    public Articles(string name)
        : base(name)
    {
    }
}

public class Questionnaire : OutputBase
{
    public Questionnaire(string name)
        : base(name)
    {
    }
}

UPDATE

Another very simple way to solve the problem is to override ToString:

public override string ToString()
{
    return String.Format("The class is: {0}\r\nThe name is: {1}", 
                         this.GetType(), _name);
}

You would call it like this:

object obj = Factory.Choose(1, "Test");
Console.WriteLine(obj);

No interface and no base class required! Well, to be precise, the base class is object of course.

Bib answered 5/7, 2013 at 14:49 Comment(0)
P
0

You have 3 choices:

1) Make Questionnaire and Article inherit from the same base class and make the type of that base class be the return type of your method.

2) Make your return type Object.

3) Make your return type Dynamic.

Pluviometer answered 5/7, 2013 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.