关于c#:返回共享方法的两种不同类型的可能对象之一

Return one of two possible objects of different types sharing a method

我有两个班:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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和一个名称。

此方法必须返回这两个类之一的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()


为什么没有一个定义了Output的基类呢?然后返回基地。

1
2
3
public abstract class BaseType {
    public abstract void Output();
}

ArticlesQuestionaire都应该继承这个BaseType

1
2
3
4
5
6
7
public class Articles : BaseType {
  // Output method here
}

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

然后你可以做:

1
2
3
4
5
6
7
8
9
10
11
12
13
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来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
public interface IInterface {
    void Output();
}

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

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

然后您必须修改choose方法以返回IInterface,而不是BaseType。无论你选择哪一个,都取决于你自己。

注意:即使不能更改原始类,也可以在使用dynamic之前使用这些方法,方法是提供实现接口的包装类,并继承原始调用或将调用转发到相应的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();}

}


像这样的怎么样:

1
2
3
4
5
6
7
8
public interface IHasOutput
{
    void Output();
}

public class Articles : IHasOutput

public class Questionnaire : IHasOutput

然后:

1
public static IHasOutput Choose...

当然,除了IHasOutput,您可以随意调用接口,我只是不知道该如何调用它。这就是接口的用途。共享一个公共接口的两个不同的具体实现。现在,当您调用它时,可以执行以下操作:

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

而返回的具体实现并不重要。您知道它实现了一个公共接口。


这里提供的答案很好,但有一件事我不喜欢,那就是参数x,它选择应该创建什么类型。这就产生了使用魔法数字的效果,即使以后你也会头疼。

您可以利用这里的仿制药,即制作方法Choose

1
2
3
4
5
6
7
8
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 });
    }

用途:

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

演示

编辑

正如注释x中提到的@olivierjacot descombes,选择类型可能是用户输入。在这种情况下,您可以使用各自的值创建enum

1
2
3
4
enum ArticleType {
    Articles = 1,
    Questionnaire = 2
}

并有EDOCX1[16]过载:

1
2
3
4
5
6
7
8
9
10
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);
    }
}

用途:

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

这使您有可能保持代码的整洁,并对将来的维护有用(例如,您可以在Choose中更改类创建的逻辑)。

另外,您可能有兴趣了解更多关于工厂模式的信息。


除非它们共享同一个基类或接口,否则您会非常困惑于objectdynamic


解决这个问题最灵活的方法是编写一个接口以及一个实现它的抽象基类。这样,如果基类在非常特殊的情况下不能满足您的需要,或者如果某个类已经从另一个类派生,您就可以自由地从基类派生类或直接实现接口。还可以使方法Output为虚拟的;这使您能够在必要时重写它。同时使name受到保护;这使您能够在派生类中使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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)
    {
    }
}

更新

解决这个问题的另一个非常简单的方法是覆盖ToString

1
2
3
4
5
6
7
public override string ToString()
{
    return String.Format("The class is: {0}

The name is: {1}"
,
                         this.GetType(), _name);
}

你可以这样称呼它:

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

不需要接口和基类!准确地说,基类是object


您有三个选择:

1)使调查问卷和项目从同一个基类继承,并使该基类的类型成为方法的返回类型。

2)生成返回类型对象。

3)使您的返回类型具有动态性。