简体   繁体   English

C# 返回不同的类型?

[英]C# Return different types?

I got something like this:我得到了这样的东西:

public [What Here?] GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return radio; or return computer; or return hello //should be possible?!      
}

I have a method which returns different types of instances (classes).我有一个方法可以返回不同类型的实例(类)。

How can I do this and ofcourse later work with the variables, eg radio.Play() and so on?我怎样才能做到这一点,当然以后radio.Play()使用变量,例如radio.Play()等等?

Do I need to use generics, and if so how?我是否需要使用泛型,如果需要,如何使用?

Here is how you might do it with generics:以下是您可以如何使用泛型做到这一点:

public T GetAnything<T>()
{
   T t = //Code to create instance

   return t;
}

But you would have to know what type you wanted returned at design time.但是你必须知道你想要在设计时返回什么类型。 And that would mean that you could just call a different method for each creation...这意味着您可以为每个创建调用不同的方法......

If there is no common base-type or interface, then public object GetAnything() {...} - but it would usually be preferable to have some kind of abstraction such as a common interface.如果没有通用的基类型或接口,则public object GetAnything() {...} - 但通常最好具有某种抽象,例如通用接口。 For example if Hello , Computer and Radio all implemented IFoo , then it could return an IFoo .例如,如果HelloComputerRadio都实现了IFoo ,那么它可以返回一个IFoo

use the dynamic keyword as return type.使用 dynamic 关键字作为返回类型。

 private dynamic getValuesD<T>()
    {
        if (typeof(T) == typeof(int))
        {
            return 0;
        }
        else if (typeof(T) == typeof(string))
        {
            return "";
        }
        else if (typeof(T) == typeof(double))
        {
            return 0;
        }
        else
        {
            return false;
        }
    }

        int res = getValuesD<int>();
        string res1 = getValuesD<string>();
        double res2 = getValuesD<double>();
        bool res3 = getValuesD<bool>();

// dynamic keyword is preferable to use in this case instead of an object type // 在这种情况下最好使用动态关键字而不是对象类型

// because dynamic keyword keeps the underlying structure and data type so that // you can directly inspect and view the value. // 因为动态关键字保留了底层结构和数据类型,所以 // 您可以直接检查和查看值。

// in object type, you have to cast the object to a specific data type to view // the underlying value. // 在对象类型中,您必须将对象强制转换为特定数据类型才能查看 // 底层值。

regards,问候,

Abhijit阿比吉特

Marc's answer should be the correct one, but in .NET 4 you couldn't also go with dynamic type. Marc 的答案应该是正确的,但是在 .NET 4 中,您也不能使用动态类型。

This should be used only if you have no control over the classes you return and there are no common ancestors ( usually with interop ) and only if not using dynamic is a lot more painful then using(casting every object in every step :) ).仅当您无法控制返回的类并且没有共同的祖先(通常使用 interop )并且仅当不使用 dynamic 比使用(在每个步骤中投射每个对象:) 更痛苦时才应该使用它)。

Few blog post trying to explain when to use dynamic: http://blogs.msdn.com/b/csharpfaq/archive/tags/dynamic/很少有博客文章试图解释何时使用动态: http : //blogs.msdn.com/b/csharpfaq/archive/tags/dynamic/

public dynamic GetSomething()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio(); 
     return // anyobject

}

If you can make a abstract class for all the possibilities then that is highly recommended:如果您可以为所有可能性创建一个抽象类,那么强烈建议您这样做:

public Hardware GetAnything()
{
     Computer computer = new Computer();

     return computer;    
}

abstract Hardware {

}

class Computer : Hardware {

}

Or an interface:或者一个接口:

interface IHardware {

}

class Computer : IHardware {

}

If it can be anything then you could consider using "object" as your return type, because every class derives from object.如果它可以是任何东西,那么您可以考虑使用“对象”作为您的返回类型,因为每个类都从对象派生。

public object GetAnything()
{
     Hello hello = new Hello();

     return hello;    
}

To build on the answer by @RQDQ using generics, you can combine this with Func<TResult> (or some variation) and delegate responsibility to the caller:要使用泛型建立@RQDQ 的答案,您可以将其与Func<TResult> (或某些变体)结合起来,并将责任委托给调用者:

public T GetAnything<T>(Func<T> createInstanceOfT)
{
    //do whatever

    return createInstanceOfT();
}

Then you can do something like:然后你可以做这样的事情:

Computer comp = GetAnything(() => new Computer());
Radio rad = GetAnything(() => new Radio());

You can have the return type to be a superclass of the three classes (either defined by you or just use object ).您可以将返回类型设为三个类的超类(由您定义或仅使用object )。 Then you can return any one of those objects, but you will need to cast it back to the correct type when getting the result.然后您可以返回这些对象中的任何一个,但是在获得结果时您需要将其转换回正确的类型。 Like:像:

public object GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return radio; or return computer; or return hello //should be possible?!      
}

Then:然后:

Hello hello = (Hello)getAnything(); 

You could just return an Object as all types are descended from Object.您可以只返回一个 Object,因为所有类型都来自 Object。

public Object GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return radio; or return computer; or return hello //should be possible?!      
}

You could then cast to its relevant type:然后,您可以转换为其相关类型:

Hello hello = (Hello)GetAnything();

If you didn't know what the type was going to be then you could use the is keyword.如果您不知道类型将是什么,那么您可以使用is关键字。

Object obj = GetAnything();
if (obj is Hello) {
    // Do something
}

This being said I would be reluctant to write code like that.话虽如此,我不愿意写那样的代码。 It would be much better to have an interface which is implemented by each of your classes.拥有一个由您的每个类实现的接口会好得多。

public ISpeak GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return radio; or return computer; or return hello //should be possible?!      
}

interface ISpeak 
{
   void Speak();
}

and have each of your classes implement the interface:并让您的每个类实现接口:

public class Hello : ISpeak
{
    void Speak() {
        Console.WriteLine("Hello");
    }
}

You could then do something like:然后,您可以执行以下操作:

GetAnything().Speak();

You have a few options depending on why you want to return different types.根据您想要返回不同类型的原因,您有几个选项。

a) You can just return an object, and the caller can cast it (possibly after type checks) to what they want. a) 你可以只返回一个对象,调用者可以将它(可能在类型检查之后)转换为他们想要的。 This means of course, that you lose a lot of the advantages of static typing.这当然意味着您失去了静态类型的许多优点。

b) If the types returned all have a 'requirement' in common, you might be able to use generics with constriants . b) 如果返回的类型都有一个共同的“要求”,您也许可以使用带有约束的泛型

c) Create a common interface between all of the possible return types and then return the interface. c) 在所有可能的返回类型之间创建一个公共接口,然后返回该接口。

d) Switch to F# and use pattern matching and discriminated unions. d) 切换到 F# 并使用模式匹配和可区分联合。 (Sorry, slightly tongue in check there!) (对不起,在那里稍微检查一下舌头!)

Let the method return a object from a common baseclass or interface.让该方法从公共基类或接口返回一个对象。

public class TV:IMediaPlayer
{
   void Play(){};
}

public class Radio:IMediaPlayer
{
   void Play(){};
}

public interface IMediaPlayer
{
   void Play():
}

public class Test
{
  public void Main()
  {
     IMediaPlayer player = GetMediaPlayer();
     player.Play();
  }


  private IMediaPlayer GetMediaPlayer()
  {
     if(...)
        return new TV();
     else
        return new Radio();
  }
}

Rick's solution is the 'best' way to go in most cases.在大多数情况下,Rick 的解决方案是“最佳”方式。 Sometimes when that's not available you want to use object as base type.有时,当它不可用时,您想使用 object 作为基本类型。 And you could use the method like this:你可以使用这样的方法:

public object GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return hello; // or computer or radio   
}

To use it, you will want to use the as operator, like this:要使用它,您需要使用as运算符,如下所示:

public void TestMethod()
{
    object anything = GetAnything();
    var hello = anything as Hello;
    var computer = anything as Computer;
    var radio = anything as Radio;

    if (hello != null)
    {
        // GetAnything() returned a hello
    }
    else if (computer != null)
    {
        // GetAnything() returned a computer
    }
    else if (radio != null)
    {
        // GetAnything() returned a radio
    }
    else
    {
        // GetAnything() returned... well anything :D
    }
}

In your case you want to call a method play.在您的情况下,您想调用方法播放。 So this'd seem more appropriate:所以这似乎更合适:

interface IPlayable
{
    void Play();
}

class Radio : IPlayable
{
    public void Play() { /* Play radio */ }
}

class Hello : IPlayable
{
    public void Play() { /* Say hello */ }
}

class Computer : IPlayable
{
    public void Play() { /* beep beep */ }
}

public IPlayable GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return hello; // or computer or radio   
}

You could use external class, set the properties types as you wish, then use it in your function.您可以使用外部类,根据需要设置属性类型,然后在您的函数中使用它。

public class MultipleOpjects
{
    private List<string> _ObjectOne;
    public List<string> ObjectOne {
        get { return _ObjectOne; }
        set { _ObjectOne = value; }
    }
    private List<object> _ObjectTwo;
    public List<object> ObjectTwo {
        get { return _ObjectTwo; }
        set { _ObjectTwo = value; }
    }
    private object _ObjectThree;
    public object ObjectThree {
        get { return _ObjectThree; }
        set { _ObjectThree = value; }
    }
}
public MultipleOpjects GetAnything()
{
    MultipleOpjects Vrble = new MultipleOpjects();
    Vrble.ObjectOne  = SomeThing1;
    Vrble.ObjectTwo = SomeThing2;
    Vrble.ObjectThree = SomeThing3;

    return Vrble;      
}

I have an idea for returning multiple types ....... 我有一个返回多种类型的想法.......

public object GetAnything(object o)
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();
     if(o == Hello){return hello;}
     if(o == Computer {return computer;}
     if(o == Radio) {return radio;}
}

Defining a single type for all is not always possible.为所有人定义单一类型并不总是可行的。 Even if when you can, the implementation is rarely easy.即使可以,实现也很少容易。 I prefer to use out parameters.我更喜欢使用out参数。 The only caveat is that you need to know all the return types in advanced:唯一需要注意的是,您需要提前了解所有返回类型:

public void GetAnything(out Hello h, out Computer c, out Radio r)
{
     /// I suggest to:
     h = null;
     c = null;
     r = null; 
     // first, 

     // Then do whatever you have to do:
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();
}

The return type can be a void , or something else, like bool an int or a predefined enum which can help you check for exceptions or different cases wherever the method is used.返回类型可以是void或其他类型,例如boolint或预定义的enum ,它们可以帮助您在使用该方法的任何地方检查异常或不同情况。

It's a sample using Generic Types.这是一个使用泛型类型的示例。

public T GetAnything<T>() where T : class, new()
    => new T();

And you'll use this method calling this way:您将使用这种方式调用此方法:

var hello = GetAnything<Hello>();

In this case, you can use an interface to specify a type to pass as parameter.在这种情况下,您可以使用接口指定要作为参数传递的类型。

public T GetAnything<T>() where T : ISomeInterface, new()
    => new T();

You must have a parameterless construtor in each class to use new() constraint.您必须在每个类中都有一个无参数构造函数才能使用 new() 约束。

Follow the full sample:遵循完整示例:

internal sealed class Program
{
    private static void Main(string[] args)
    {
        GetAnything<Hello>().Play();
        GetAnything<Radio>().Play();
        GetAnything<Computer>().Play();
    }

    private static T GetAnything<T>() where T : ISomeInterface, new()
        => new T();
}

internal interface ISomeInterface
{
    void Play();
}

internal sealed class Hello : ISomeInterface
{
    // parameterless constructor.
    public Hello() { }
    public void Play() => Console.WriteLine("Saying hello!");
}

internal sealed class Radio : ISomeInterface
{
    // parameterless constructor.
    public Radio() { }
    public void Play() => Console.WriteLine("Playing radio!");
}

internal sealed class Computer : ISomeInterface
{
    // parameterless constructor.
    public Computer() { }
    public void Play() => Console.WriteLine("Playing from computer!");
}

My post here is strictly related to Blazor v5 but should work in 3.x as well.我的帖子与Blazor v5 严格相关,但也适用于 3.x。 Additionally, I'm using these methods with bootstrap 4.5 and 5.0 beta 1 but you could easily adapt it to use style's instead of classes or use your own classes.此外,我在 bootstrap 4.5 和 5.0 beta 1 中使用这些方法,但您可以轻松地调整它以使用样式而不是类或使用您自己的类。

To those recommending dynamic, I thank you.对于那些推荐动态的人,我谢谢你。 The dynamic type seems like it can be very valuable when used correctly.如果使用得当,动态类型似乎非常有价值。 Most of the time you'll probably use an interface but this wasn't plausible for me.大多数情况下,您可能会使用接口,但这对我来说并不合理。 I went ahead and updated my project with a dynamic return type and it's working great while being the quickest, cleanest solution.我继续使用动态返回类型更新了我的项目,它运行良好,同时是最快、最干净的解决方案。

I previously added the following extension methods to boolean types to help me avoid long ternary operators in razor page code.我之前向布尔类型添加了以下扩展方法,以帮助我避免剃刀页面代码中的长三元运算符。 Here are the 3 main extension methods I use to accomplish it:以下是我用来完成它的 3 种主要扩展方法:

public static T Then<T>(this bool value, T result) => value ? result : default;    
public static T Then<T>(this bool value, T thenResult, T elseResult) => value ? thenResult : elseResult;
public static T Else<T>(this bool value, T result) => !value ? result : default;

The following are examples of that implementation:以下是该实现的示例:

<div class="@Hidden.Then("d-none")">
    Hidden content...
</div>

Note : ErrorOrWarning would hide the content if there was no error/warning so I could default it to yellow/italic but this is an example so please use your imagination:注意:如果没有错误/警告,ErrorOrWarning 将隐藏内容,因此我可以将其默认为黄色/斜体,但这是一个示例,因此请发挥您的想象力:

<div class="@ErrorOrWarning.Else("d-none")" style="@Error.Then("color:red;font-weight:bold;","color:yellow;font-style:italic;")">
    Error/Warning content...
</div>

This is the typical implementation without the extension methods.这是没有扩展方法的典型实现。 It's common to see this technique in Blazor guides/tutorials/videos online.在 Blazor 指南/教程/视频在线中看到这种技术是很常见的。 There are cleaner ways to handle it but this is the basic idea:有更简洁的方法来处理它,但这是基本思想:

<div class="@(ErrorOrWarning ? "" : "d-none")" style="@(Error ? "color:red;font-weight:bold;" : "color:yellow;font-style:italic;")">
    Error/Warning content...
</div>

While this might not seem like too big a difference, if you have a lot of dynamic content/styles driving your page, it can get really messy.虽然这看起来差别不大,但如果您有很多动态内容/样式驱动您的页面,它可能会变得非常混乱。 With those 3 lines of code you can enhance the readability and cleanliness and it really does reduce risk of typos.使用这 3 行代码,您可以提高可读性和清洁度,并且确实降低了拼写错误的风险。 Adding two more extension methods, you can reduce the risk even further Note: Again, this is using bootstrap class "d-none" for the style "display:none!important" but you could just as easily replace with your own usage :添加另外两个扩展方法,您可以进一步降低风险注意:同样,这是使用引导程序类“d-none”作为样式“display:none!important”,但您可以轻松地替换为您自己的用法

public static string ThenShow(this bool value) => value ? "" : "d-none";
public static string ThenHide(this bool value) => value ? "d-none" : "";

The limitation I previously faced was when using the overloaded Then(thenResult, elseResult) , each of the parameters must be of the same type.我之前面临的限制是在使用重载的Then(thenResult, elseResult) 时,每个参数必须是相同的类型。 99% of the time this is fine. 99% 的情况下这很好。 Actually, another 0.5% of the time it's still okay because you can probably solve it quickly with a .ToString() or an explicit cast.实际上,另外 0.5% 的时间它仍然可以,因为您可能可以使用 .ToString() 或显式转换快速解决它。

What I ran into, and what took me to this post was: I have a control you can imagine as a button.我遇到了什么,并把我带到了这篇文章是:我有一个你可以想象成一个按钮的控件。 There is an Enum property allowing the user to select an icon to display.有一个 Enum 属性允许用户选择要显示的图标。 The selected Enum dynamically populates a readonly MarkupString property.选定的 Enum 动态填充只读MarkupString属性。 As an alternative option, they can use the ChildContent (or IconContent in my example) of type RenderFragment .作为替代选项,他们可以使用RenderFragment类型的 ChildContent(或在我的示例中为 IconContent)。 This will let them manually add anything they want (maybe an iFrame to stackoverflow haha) but my intention is for them to add style, most likely in the form of an icon.这将让他们手动添加他们想要的任何东西(可能是一个 iFrame 到 stackoverflow 哈哈)但我的目的是让他们添加样式,最有可能以图标的形式。

I know I can cast/convert one to the other however my existing extension method is so clean and simple, it would be great to be able to use pass the MarkupString and RenderFragment together as parameters, conditionally output to the razor page.我知道我可以将一个转换/转换为另一个,但是我现有的扩展方法非常干净和简单,能够将MarkupStringRenderFragment作为参数一起使用,有条件地输出到剃刀页面会很棒。 So, thanks to this post, I changed my Then(thenResult, elseResult) extension methods to use unique generic parameter types and return a dynamic type like so:所以,多亏了这篇文章,我改变了我的Then(thenResult, elseResult)扩展方法来使用唯一的泛型参数类型并返回一个动态类型,如下所示:

public static dynamic Then<T,E>(this bool value, T thenResult, E elseResult) => value ? thenResult : elseResult;

Now in my razor page I have a very simple line for the icon output.现在在我的剃刀页面中,我有一个非常简单的图标输出行。 Note: IconContent is a RenderFragment and IconMarkup is a MarkupString .注意: IconContent 是一个RenderFragment , IconMarkup 是一个MarkupString

@((@IconContent == null).Then(IconMarkup, IconContent))

And because I love extension methods and I'm typing this up, I took it a step further with another extension method:因为我喜欢扩展方法并且我正在输入它,所以我使用另一种扩展方法更进一步:

public static bool IsNull(this RenderFragment value) => value == null;

Which enables the extremely clean and simple:这使得非常干净和简单:

@IconContent.IsNull().Then(IconMarkup, IconContent)

Here's the extra extension method I mentioned above which converts a string to a MarkupString.这是我上面提到的将字符串转换为 MarkupString 的额外扩展方法。 It might be overkill but I like it.这可能有点矫枉过正,但我​​喜欢它。

public static MarkupString ToMarkup(this string value) => (MarkupString)value;

Let me know if you have a better recommendation or if you think I'm doing something wrong.如果您有更好的建议,或者您认为我做错了什么,请告诉我。 I'm sure this post makes it seem like I overuse extension methods but I really don't.我确信这篇文章让我觉得我过度使用了扩展方法,但我真的没有。 I keep their use limited to outcomes such as I've outlined in this post.我将它们的使用限制在我在这篇文章中概述的结果中。

As already mentioned in other answers before, it is preferable to have some kind of abstraction as a common interace or abstract base class.正如之前其他答案中已经提到的,最好将某种抽象作为公共接口或抽象基类。

In some cases it is not possible or not appropiate to introduce such abstraction.在某些情况下,不可能或不适合引入这种抽象。 As alternative some answers suggests returning an object and cast it back to the original type: public object GetAnything() {...} .作为替代,一些答案建议返回一个object并将其转换回原始类型: public object GetAnything() {...}

The only "problem" with that solution is, that the caller have to know of what possible type the object can be.该解决方案的唯一“问题”是,调用者必须知道object可能是什么类型。 To avoid that problem we can introduce an object that provides an "interface" that directly communicates the possible object types to the caller.为了避免这个问题,我们可以引入一个对象,该对象提供一个“接口”,将可能的对象类型直接传达给调用者。

The following code uses a struct to avoid additional heap allocation.以下代码使用struct来避免额外的堆分配。 The DynamicObject just contains one object and the needed methods. DynamicObject只包含一个object和所需的方法。 You may want to add null checks in the DynamicObject constructors.您可能希望在DynamicObject构造函数中添加空检查。

// Usage of DynamicObject.
public void ExampleUsage()
{
    DynamicObject dynamicObject = GetAnything();
    if (dynamicObject.TryGetRadio(out Radio radio))
        radio.Play();
    else
        ; // ...
}

public DynamicObject GetAnything()
{
    Random rnd = new Random();
    switch (rnd.Next(0, 3))
    {
        case 0:
            return new DynamicObject(new Hello());
        case 1:
            return new DynamicObject(new Computer());
        case 2:
            return new DynamicObject(new Radio());
        default:
            throw new InvalidOperationException(); // Not possible.
    }
}

// Implementation of DynamicObject.
public struct DynamicObject
{
    private readonly object _value;

    public DynamicObject(Hello hello) => _value = hello;

    public DynamicObject(Computer computer) => _value = computer;

    public DynamicObject(Radio radio) => _value = radio;

    public bool TryGetHello(out Hello hello) => TryGetAsConcreteObject(out hello);

    public bool TryGetComputer(out Computer computer) => TryGetAsConcreteObject(out computer);

    public bool TryGetRadio(out Radio radio) => TryGetAsConcreteObject(out radio);

    private bool TryGetAsConcreteObject<T>(out T value)
    {
        if (_value is T concreteObject)
        {
            value = concreteObject;
            return true;
        }
        else
        {
            value = default(T);
            return false;
        }
    }
}

create an object then put all your data in it.创建一个对象,然后将所有数据放入其中。 Return that object.返回那个对象。 Cast the object as an array (Array)yourObject then cast the values of the array as an integer or what you want.将对象转换为数组 (Array)yourObject,然后将数组的值转换为整数或您想要的值。

class Program
{
    static void Main(string[] args)
    {   
        object data = MyMethod();
        Array dataarray = (Array)data;
        string astring = (string) dataarray.GetValue(0);
        int aninteger = (int)dataarray.GetValue(1);
        Console.WriteLine(astring);
        Console.WriteLine(aninteger);
    }

    static object MyMethod()
    {   
        /// create an object array

        object[] myarray = new object[2];

        /// put your values in it (remeber their order to cast them right later)

        myarray[0] = "any string";
        myarray[1] = 3;
        
        /// convert the object array to a singel object
        
        object _myarray = (object) myarray;
        return _myarray;
    }

}

This method of retuning multipile values as one object is very helpful for building a program with ParameterizedThreadStart.这种将多个值作为一个对象重新调整的方法对于使用 ParameterizedThreadStart 构建程序非常有帮助。 (I'm sorry for bad my explination but the code works and everyone should be able to understand it) (我很抱歉我的解释不好,但代码有效,每个人都应该能够理解它)

May be you need "dynamic" type?您可能需要“动态”类型吗?

public dynamic GetAnything()
{
     Hello hello = new Hello();
     Computer computer = new Computer();
     Radio radio = new Radio();

     return /*what boject you needed*/ ;`enter code here`   
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM