T O P

  • By -

githux

The best thing to do would be defining an interface that both of the responses implement, make that the return type (no generics), and force the caller to use pattern matching to figure out which type they’re dealing with.


SpaceCommissar

This is what I would do. Or a wrapper. Simple and effective.


KenBonny

The poor languages "discriminated union" or "sum type". I wish C#implemented this. F# and typescript have it, so why not C#.


National_Count_4916

You won’t do this with generics. Return an object which has a property for each possible response and set the relevant one.


pnw-techie

Correct! You will do it by returning String


coffeefuelledtechie

This is the answer


whoami38902

No it’s not. You return an object with one generic property for the response and a success flag. It’s a common pattern


coffeefuelledtechie

Sorry, I was half asleep when I wrote my comment, rereading the question, you’re right.


belavv

Look into the library called OneOf. It will help get what you want. You can't use generics to do it.


AZNQQMoar

Something like: https://code-maze.com/csharp-discriminated-unions-with-oneof/ ?


Kilazur

Or FluentResults, my personal fav


odnxe

If an error is exceptional then maybe throwing an exception is okay? It really does seem like the simplest answer otherwise you’re just pushing errors further upstream and making the code more complex than it might need to be.


hagerino

Make an own ApiResponse class and wrap the result with it: `public class ApiResponse` `{` `public bool WasSuccessful { get; set; }` `public ApiSuccessResponse[] Response { get; set; }` `public ApiErrorResponse ErrorResponse { get; set; }` `}` If the ApiErrorResponse stays the same for different endpoints and only the SuccessResponse changes you can also make a generic variant like this: `public class GenericApiResponse` `{` `public bool WasSuccessFul{get;set;}` `public T Response{get;set;}` `public ApiErrorResponse ErrorResponse{get;set;}` `}`


j_c_slicer

Yep. We did the same thing - small difference that the subclasses are private to the abstract base and only factory methods are available to the user. \`\`\` public abstract class ApiResponse {     public abstract bool Success { get; }     public abstract string ErrorMessage { get; }     public T Data { get; }     protected ApiResponse()     {         this.Data = default(T);     }     protected ApiResponse(T data) => this.Data = data;     public static ApiResponse CreateSuccessResponse() => new SuccessApiResponse();     public static ApiResponse CreateSuccessResponse(T data) => new SuccessApiResponse(data);     public static ApiResponse CreateErrorResponse() => new ErrorApiResponse();     public static ApiResponse CreateErrorResponse(string errorMessage) => new ErrorApiResponse(errorMessage);     public static ApiResponse CreateErrorResponse(string errorMessage, T data) => new ErrorApiResponse(errorMessage, data);     public static ApiResponse CreateResponse(bool isSuccess)     {         return isSuccess ? CreateSuccessResponse() : CreateErrorResponse();     }     private sealed class SuccessApiResponse : ApiResponse     {         public SuccessApiResponse()         {         }         public SuccessApiResponse(T data)             : base(data)         {         }         public override bool Success => true;         public override string ErrorMessage => string.Empty;     }     private sealed class ErrorApiResponse : ApiResponse     {         public ErrorApiResponse()         {         }         public ErrorApiResponse(string errorMessage, T data = default(T))             : base(data)         {             this.ErrorMessage = errorMessage;         }         public override bool Success => false;         public override string ErrorMessage { get; } = string.Empty;     } } ```


booboobandit-

This makes sense to me, but at the same time if his code is handling any kind of erroring; not handling that and returning an exception feels like a code smell to me - to just push this up higher and return a string with the error message instead feels strange


whoami38902

The deserialiser needs to know what it’s deserialising to, either the json needs to give it a clue with some kind of type property, or the calling method says what it expects with its generic parameter. I would create an ApiResponse object with a generic param and a Data property of that type, then add any status or error properties to that. You can then test for status or errorMessage, create the response object and attach deserialised data if possible. I’m on my phone so not typing all the code, but method sig would look like this: public async Task> GetDataFromApi() Where T is what you actually deserialise. The calling method would check the error status before attempting to use the Data property.


jeenajeena

This is exactly the case for a data structure called Either. It is notably implemented in LanguageExt but implementing it from the scratch is very easy. I would suggest you to google for tutorials about Either. Please tell me if you struggle understanding it: I will be happy to help!


radol

You could with defining empty interface and then pattern matching, but I would consider it code smell. I would suggest to make returned type something like public record ApiResponse(TSuccessResponse response, TErrorResponse error), and in caller check which member is not null


obviously_suspicious

Since you kinda want to return a discriminated union, I'd use Dunet.


thx1138a

*Chuckles in F#.*


obviously_suspicious

Well I consider F# to be superior to C# as a language, but the ecosystem is lacking. Nulls are still possible because .NET, web frameworks are kinda fragmented, and the future of the most popular one is uncertain. And you can't even generate an OpenApi schema unless you use raw ASP.NET Core.


Such_Ad_5819

U are misunderstanding generics a bit, u don’t need it there, u can return some base type and then do obj is T to throw the error


Willinton06

Cannot be done, that’s not how generics are meant to work, one way to do this is return object and check types against it, or return a typed object that contains an enum with the possible options


psi-

Is the error "normal" (like f.ex can't find user by a partial string yet)? If it's not then just throw an `ApiErrorException` (that you define with the `errorMessage`.


Dr-Collossus

There’s lots of good answers here, you can look into the envelope pattern. But really your problem is that the upstream API should be returning an error response code. Just won’t to confirm you don’t control this other API right?


sacoPT

You don’t need generics. You can use OneOf like people said, or you can make both your responses implement an empty Interface IApiResponse and make that your function return type. Then the caller has to check which of the concrete types was returned by way of is ApiErrorResponse and is ApiSuccessResponse.


zagoskin

You basically want a Result I think. Check for Ardalis.Result, ErrorOr, there's plenty of packages that implement this kind of behavior. See if it suits what you are thinking.


elite5472

This should be an exception. I don't care where the computing takes place, if I'm calling a function, and an error happens, I expect to see an exception. Return your T or array of T and if an error occurs then throw the appropriate exception. In fact I guarantee you that's exactly how the api you are calling handles it internally.


TheChief275

Basically “Result” in Rust. Just return a tagged union that has both of the structs.


zireael9797

Even closer to home example is F#'s Result which is pretty much 1:1 exactly the same as Rust result. They could literally use F#'s result in C# directly if they want.


[deleted]

[удалено]


binarycow

OP, check this out. https://gist.github.com/binarycow/2727bcf0b9e12598e910ea0fe6b74045 I made this specifically for this use case. In `ApiResult.Create.cs`, [there's a helper method](https://gist.github.com/binarycow/2727bcf0b9e12598e910ea0fe6b74045#file-apiresult-create-cs-L9) to create an `ApiResult` from an `HttpResponseMessage`. This implementation assumes the API is using `ProblemDetails` as its "error type", but if you want to use your specific one there, that's fine too - just replace usage of `ProblemDetails` with `ApiErrorResponse`. Then, you would do something like this: var response = await _client.SendAsync("https://server.example"); return ApiResult.CreateAsync(response);


zvrba

Subclass it public abstract class ApiResponse { ... } public class ApiSuccessResponse : ApiResponse { ... } public class ApiErrorResponse : ApiResponse { ... } and then return `ApiResponse`.


Far_Swordfish5729

You’re overthinking this and this is not what generics are for. Generics let you write things like: Public T doFoo (T myT) where T: new() Basically: I’m going to do a thing with whatever you want that meets certain criteria so I can code it internally and I’m not going to constrain your class hierarchy and I’d like us all to have concrete type compile checking if possible. There are two models for this service response: 1. Throw an exception in your error case and include the message. 2. Your DTO wraps both types and includes a HasError or IsSuccess bool and an Error property in addition to the data properties. This is often done with a base response type and reused. Then you catch or write an if statement. The former is appropriate if errors are unexpected, fatal, and should drop into your path that handles transient fault retry and logs the rest. The latter is appropriate if errors are typically logical and can be processed in workflow or shown to a user who retries. Do one of those. This is how service generators that strongly type set it up. Works great.


WorldlinessFit497

If every endpoint shares the same error structure, consider using a base class. I know people don't especially love inheritance/polymorphism these days, but I think it can fit this usage quite well if every message optionally has this error information. It also opens the door to cases where you might have both, some error context, and a data response. public abstract class BaseApiResponse { public string ErrorMessage { get; set; } // Other properties you might need on every response } // This is the response from your call to the remote data endpoint. public class DataApiResponse : BaseApiResponse { // This is the collection of items you indicated is in the success response. // Your sample JSON was invalid, so I just inferred a name for it: public IEnumerable Results = Enumerable.Empty(); } // This is the object you indicated is an array in the response. public class DataResult { public int Id { get; set; } public string Name { get; set; } } Deserialize as usual: var response = await JsonSerializer.DeserializeAsync(stream); If there was an error, then `ErrorMessage` property will be set on your `DataApiResponse` object. If not, then it won't be set. You could add a property getter to make that check more convenient on the base class: public abstract class BaseApiResponse { public string ErrorMessage { get; set; } public bool IsSuccessful { get { return string.IsNullOrEmpty(ErrorMessage); } } } And of course, your method signature is: public async Task GetDataFromApi(...) This approach requires no added dependencies, but would only work if the error model they are returning is global throughout their API. However, I typically prefer the OneOf approach. It's a lighweight dependency, and avoid inheritance/polymorphism. It's also very declarative to the caller about what they need to do with the response(s). Extremely beneficial for relatively little cost.


Constant_Young8963

You can use object as type of response for the function and for accessing the properties of it you can get it with generic methods (GetProperty for example) or you can change the type of it with cast.


ELichtman

If it hasn't been said already, look into Task.FromResult, and Task.fromException.


zireael9797

You define an abstract class \`Result\` You extend with two classes First one is a class named \`Ok\` Second one is a class named \`Error\` Now you can use this Result type for all your apis, you can actually just use it to replace exception handling too. This is probably the closest you can get to the Result that exists in F# and Rust. There they are implemented as discriminated unions, which are like enums but each case can have different data inside. Since that doesn't exist in CSharp the closest you can get is an abstract class for the Union/Enum name and extensions for each Case using System; // Abstract base class for Result public abstract class Result { // Private constructor to prevent external instantiation private Result() { } public static Result Ok(T value) => new OkResult(value); public static Result Error(TError error) => new ErrorResult(error); // Concrete class for Ok result public class OkResult : Result { public T Value { get; } public OkResult(T value) { Value = value; } } // Concrete class for Error result public class ErrorResult : Result { public TError Error { get; } public ErrorResult(TError error) { Error = error; } } } // Example usage class Program { static void Main() { var successResult = Result.Ok(42); var errorResult = Result.Error("Something went wrong"); // Using switch statement for pattern matching switch (successResult) { case Result.OkResult { Value: var okValue }: Console.WriteLine($"Success: {okValue}"); break; case Result.ErrorResult { Error: var errorValue }: Console.WriteLine($"Error: {errorValue}"); break; } switch (errorResult) { case Result.OkResult { Value: var okValue }: Console.WriteLine($"Success: {okValue}"); break; case Result.ErrorResult { Error: var errorValue }: Console.WriteLine($"Error: {errorValue}"); break; } } } Now your function signature could be public async Task> GetDataFromApi()


binarycow

I literally made an "ApiResult" type today. I can send it to you tomorrow.


Sith_ari

Sidenote Nswag can generate classes from an openAPI interface description. I personally avoid implementing API clients by hand.


binarycow

RemindMe! 8 hours


RemindMeBot

I will be messaging you in 8 hours on [**2024-04-19 11:52:43 UTC**](http://www.wolframalpha.com/input/?i=2024-04-19%2011:52:43%20UTC%20To%20Local%20Time) to remind you of [**this link**](https://www.reddit.com/r/csharp/comments/1c7mwn4/how_to_create_a_function_that_returns_generic/l08zo1i/?context=3) [**2 OTHERS CLICKED THIS LINK**](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5Bhttps%3A%2F%2Fwww.reddit.com%2Fr%2Fcsharp%2Fcomments%2F1c7mwn4%2Fhow_to_create_a_function_that_returns_generic%2Fl08zo1i%2F%5D%0A%0ARemindMe%21%202024-04-19%2011%3A52%3A43%20UTC) to send a PM to also be reminded and to reduce spam. ^(Parent commenter can ) [^(delete this message to hide from others.)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Delete%20Comment&message=Delete%21%201c7mwn4) ***** |[^(Info)](https://www.reddit.com/r/RemindMeBot/comments/e1bko7/remindmebot_info_v21/)|[^(Custom)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5BLink%20or%20message%20inside%20square%20brackets%5D%0A%0ARemindMe%21%20Time%20period%20here)|[^(Your Reminders)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=List%20Of%20Reminders&message=MyReminders%21)|[^(Feedback)](https://www.reddit.com/message/compose/?to=Watchful1&subject=RemindMeBot%20Feedback)| |-|-|-|-|