Вернуть один из двух возможных объектов разных типов, совместно использующих метод
У меня есть 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()
результат?
Ответы
Ответ 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) Введите тип возвращаемого значения Динамический.