Подтвердить что ты не робот

Вернуть один из двух возможных объектов разных типов, совместно использующих метод

У меня есть 2 класса:

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

и

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

Я хочу написать метод, который принимает целое число (значение 1 Articles должно быть возвращено, 2 означает Questionnaire) и имя.

Этот метод должен возвращать экземпляр одного из этих двух классов:

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

Какой тип возвращаемого значения использовать, поэтому я могу вызвать Output() результат?

4b9b3361

Ответ 1

Почему бы не создать базовый класс, который имеет Output. Затем верните базу.

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

Оба Articles и Questionaire должны наследовать этот BaseType.

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

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

Затем вы можете сделать:

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

Вы также можете достичь этого с помощью interface.

public interface IInterface {
    void Output();
}

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

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

Затем вам нужно будет изменить метод "Выбрать", чтобы вернуть IInterface, а не BaseType. Какой бы вы ни выбрали, зависит от вас.

Примечание. Даже если вы не можете изменять исходные классы, вы все равно можете использовать эти подходы, прежде чем прибегать к dynamic, предоставляя классы-оболочки, которые реализуют интерфейс, и либо наследуют оригинальные, либо переадресационные вызовы к соответствующему методу:

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

}

Ответ 2

Как насчет чего-то вроде этого:

public interface IHasOutput
{
    void Output();
}

public class Articles : IHasOutput

public class Questionnaire : IHasOutput

а затем:

public static IHasOutput Choose...

Вы можете, конечно, называть свой интерфейс чем угодно, кроме IHasOutput, я просто не знаю, как его назвать. Для этого нужны интерфейсы. Две различные конкретные реализации, которые имеют общий интерфейс. Теперь, когда вы его вызываете, вы можете сделать это:

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

и не имеет значения, какая конкретная реализация возвращается. Вы знаете, что он реализует общий интерфейс.

Ответ 3

Ответы, представленные здесь, замечательны, но мне не нравится параметр x, который выбирает, какой тип должен быть создан. Это создает использование магического номера, которое может стать больным даже для вас позже.

Здесь вы можете воспользоваться дженериками, т.е. сделать метод 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 });
    }

Использование:

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

Демо

Edit

Как @OlivierJacot-Descombes, упомянутый в комментарии x, который выбирает тип, может быть введен пользователем. В этом случае вы можете создать enum с соответствующими значениями:

enum ArticleType {
    Articles = 1,
    Questionnaire = 2
}

И перегрузка 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);
    }
}

и использование:

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

Это дает вам возможность сохранить код чистым и полезным для будущего обслуживания (например, вы можете изменить логику создания класса в Choose).

P.S. Вам может быть интересно узнать больше о factory pattern.

Ответ 4

Если они не разделяют один и тот же базовый класс или интерфейс, вы в значительной степени застреваете либо object, либо dynamic.

Ответ 5

Самый гибкий способ решения этой проблемы - написать интерфейс, а также абстрактный базовый класс, реализующий его. Таким образом, у вас есть свобода для получения класса из базового класса или для непосредственного внедрения интерфейса, если базовый класс не удовлетворяет вашим потребностям в очень особенном случае или если класс уже получен из другого класса. Также сделайте метод Output виртуальным; это позволяет вам переопределить его, если это необходимо. Также сделайте name защищенным; это позволяет использовать его в производных классах

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

Еще один очень простой способ решить проблему - переопределить ToString:

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

Вы бы назвали это следующим образом:

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

Нет интерфейса и базового класса не требуется! Ну, если быть точным, базовый класс, конечно, object.

Ответ 6

У вас есть 3 варианта:

1) Сделайте вопросник и статью наследуемыми от одного и того же базового класса и сделайте тип этого базового класса возвращаемым типом вашего метода.

2) Создайте объект возвращаемого типа.

3) Введите тип возвращаемого значения Динамический.