Home > database >  How to infer the Generics type in C#?
How to infer the Generics type in C#?

Time:03-31

I have a class that I use every time I need to return something in my APIs.

    public class OperResult<T>
    {
        public bool Ok { get; private set; }
        public T Data { get; private set; }

        // Failed result (Ok: false)
        public string ErrorCode { get; private set; }
        public string ErrorMessage { get; private set; }
       
        public static OperResult<T> Success(T data)
        {
            return new OperResult<T>(data);
        }

        public static OperResult<T> Error(string message, string code = null)
        {
            return new OperResult<T>(message, code);
        }

        private OperResult(T data = default(T))
        {
            Data = data;
            Ok = true;
        }
        private OperResult(string message, string code = null)
        {
            ErrorCode = code;
            ErrorMessage = message;
            Ok = false;
        }
    }

And one that doesn't have generics

    public class OperResult
    {
        public string ErrorCode { get; private set; }
        public string ErrorMessage { get; private set; }
        public object Data { get; private set; }
        public bool Ok { get; private set; }
        
        public static OperResult Success(object data = null)
        {
            return new OperResult(data);
        }

        public static OperResult Error(string message, string code = null)
        {
            return new OperResult(message, code);
        }

        private OperResult(object data)
        {
            Data = data;
            Ok = true;
        }
        private OperResult(string message, string code = null)
        {
            ErrorCode = code;
            ErrorMessage = message;
            Ok = false;
        }
    }

In every API response, I return these objects so I know the data and the status of the operation.

        public async Task<OperResult<IEnumerable<string>>> GetLocationNames()
        {
            var locations = await locationService.GetAll(AuthState);
            if (location != null)
               return OperResult<IEnumerable<string>>.Success(locations.Select(u => u.name));
            else
               return OperResult<IEnumerable<string>>.Error("No location data found");

        }

I would like to be able not to write the Generics part every time I use it. Maybe something like:

        public async Task<OperResult<IEnumerable<string>>> GetLocationNames()
        {
            var locations = await locationService.GetAll(AuthState);
            if (location != null)
               return OperResult.Success(locations.Select(u => u.name)); // infer <IEnumerable<string>>
            else
               return OperResult.Error("No location data found"); // here too

        }

Is this possible?

CodePudding user response:

First, I noticed that the non-generic OperResult can inherit from OperResult<object>. I recommend doing that to avoid code duplication.

The below is inspired by Simulating Return Type Inference in C#.

Create a DelayedResult<T> as a wrapper for some value. This will be useful later on:

public readonly struct DelayedResult<T>
{
    public T Value { get; }

    public DelayedResult(T value)
    {
        Value = value;
    }
}

In the non-generic OperResult, create an Error and a generic Success method. These are what clients will use to create OperResult<T> and OperResults. Note that these will return the DelayedResult declared earlier:

public static DelayedResult<T> Success<T>(T ok) =>
    new DelayedResult<T>(ok);

public static DelayedResult<Error> Error(string error, string code = null) =>
    new DelayedResult<Error>(new Error(error, code));

where Error is just a simple type containing the error and code. It could be as simple as a record:

record Error(string Message, string Code);

In OperResult<T>, create implicit conversion operators that converts from DelayedResult to actual OperResult<T>s:

public static implicit operator OperResult<T>(DelayedResult<T> ok) =>
    new OperResult<T>(ok.Value);

public static implicit operator OperResult<T>(DelayedResult<Error> error) =>
    new OperResult<T>(error.Value.Message, error.Value.Code);

Note that you should never have OperResult<Error> (doesn’t make sense anyway), otherwise the above two overloads will be ambiguous.

In the non-generic OperResult, you can also have:

public static implicit operator OperResult(DelayedResult<object> ok) =>
    new OperResult(ok.Value);

public static implicit operator OperResult(DelayedResult<Error> error) =>
    new OperResult(error.Value.Msg, error.Value.Code);

Now you can do

    public async Task<OperResult<IEnumerable<string>>> GetLocationNames()
    {
        var locations = await locationService.GetAll(AuthState);
        if (location != null)
           return OperResult.Success(locations.Select(u => u.name));
        else
           return OperResult.Error("No location data found");

    }

Full code:

public record Error(string Message, string Code);
public readonly struct DelayedResult<T>
{
    public T Value { get; }

    public DelayedResult(T value)
    {
        Value = value;
    }
}

public class OperResult: OperResult<object>
{
    private OperResult(object data) : base(data) {}
    private OperResult(string message, string code = null): base(message, code) {}

    public static implicit operator OperResult(DelayedResult<object> ok) =>
        new OperResult(ok.Value);

    public static implicit operator OperResult(DelayedResult<Error> error) =>
        new OperResult(error.Value.Message, error.Value.Code);

    public static DelayedResult<T> Success<T>(T ok) =>
        new DelayedResult<T>(ok);

    public static DelayedResult<Error> Error(string message, string code = null) =>
        new DelayedResult<Error>(new Error(message, code));
}

public class OperResult<T>
{
    public bool Ok { get; private set; }
    public T Data { get; private set; }
    public string ErrorCode { get; private set; }
    public string ErrorMessage { get; private set; }

    public static implicit operator OperResult<T>(DelayedResult<T> ok) =>
        new OperResult<T>(ok.Value);

    public static implicit operator OperResult<T>(DelayedResult<Error> error) =>
        new OperResult<T>(error.Value.Message, error.Value.Code);


    protected OperResult(T data = default(T))
    {
        Data = data;
        Ok = true;
    }
    protected OperResult(string message, string code = null)
    {
        ErrorCode = code;
        ErrorMessage = message;
        Ok = false;
    }
}

CodePudding user response:

Calling static from OperResult<T> will always require you to specify the generic part. However, if its a method that returns OperResult<T> from different class, it may infer the generic via method parameters (assuming OperResult<T> constructor is public):

public static class OperHelper
{
    public static OperResult<T> Success<T>(T data)
    {
        return new OperResult<T>(data);
    }
}

The method above can be called by OperHelper.Create(myPayload). However, it relies on the fact that myPayload can be used to infer what type is T. This will allow you to handle the "success" part of the OperResult.

The proposed solution below involves breaking down the OperResult<T> into several class. It assumes the following:

  • "Error" always have the bool Ok property as false and vice versa
  • "Error" doesnt need to bring payload/data

First, you have class OperResult, it only holds the contract for bool Ok:

public abstract class OperResult
{
    public abstract bool Ok { get; }
}

Then, we extend the OperResult into two - OperSuccess and OperError:

public class OperSuccess<T> : OperResult
{
    public override bool Ok { get { return true;} }
    public T Data { get; private set; }
    
    public OperSuccess(T data)
    {
        this.Data = data;
    }
}


public class OperError : OperResult
{
    public override bool Ok { get { return false;} }
    public string ErrorCode { get; private set; }
    public string ErrorMessage { get; private set; }
    
    public OperError(string message, string code = null)
    {
        this.ErrorCode = code;
        this.ErrorMessage = message;
    }
}

This way, we can expand our OperHelper into accomodating the "error" part.

public static class OperHelper
{
    public static OperSuccess<T> Success<T>(T data)
    {
        return new OperSuccess<T>(data);
    }
    
    public static OperError Error(string message, string code = null)
    {
        return new OperError(message, code);
    }
}

This way, it allow us to call either OperHelper.Success(myObject) or OperHelper.Error("my error message").

Note:

  • Yes, none stops you from creating either OperSuccess and OperError manually
  • Yes, it works on .Net 4.7
  • Related