Search code examples
c#genericstypesabstractnullable

Nullable <T> as parameter


I've been trying to mess around with generic types and abstractions for a personnal library project but i'm facing a problem. I found this post that was kinda like what i wanted to do, but I wanted to push it a step further. because i wanted to constrain my function with generic parameter to only few types something like :

public static T Read<T>(T? min, T? max) where T: int, float, double, anything i want
{
}

I know it's impossible in this way, but i'm trying to find some workarounds to achieve something similar

I tried to set to use: T? but I get a message that says that T must not be nullable to be used as a parameter. As you can see from :

where F : ConsoleReadType<T>

I'm basically trying to allow only inherited classes to run.

public abstract class ConsoleReadType<T>
{
    public abstract T Read();
    public abstract T Read(T? min, T? max);
    public virtual F ReadUntilCorrect<F>(Func<F> FunctionToRun, string message = "") /*where F : ConsoleReadType<T>*/
    {
        while (true)
        {
            try
            {
                return FunctionToRun();
            }
            catch (ConsoleInputException)
            {
                if (!string.IsNullOrEmpty(message))
                    ConsoleWrite.Error(message);
            }
        }
    }
}

public class ConsoleReadDouble : ConsoleReadType<double>
{
    public override double Read()
    {
        if (!double.TryParse(Console.ReadLine().Replace(".", ","), out double ret))
        {
            throw new ConsoleInputException();
        }
        return ret;
    }
    public override double Read(double? min, double? max)
    {
        if (!double.TryParse(Console.ReadLine().Replace(".", ","), out double ret))
        {
            throw new ConsoleInputException("invalid input format");
        }
        if (min.HasValue && ret < min || max.HasValue && ret > max)
        {
            throw new ConsoleInputException("input value should be between: " + min + " and " + max);
        }
        return ret;
    }
}

So the main questions are:
1. Is there a way to set nullable T variables in abstract, or is there a better way to achieve what i'm trying to do?
2. Can I allow only certain functions with a where statement ?
3. Is there a way to make these classes static in the end to be used as a helper without having to instanciate them?
4. I'm also interested by any advice you could give me about my code

Thanks a lot.


Solution

  • You could use just this:

    // add where T: struct so that only structs (int, double, etc) can be used
    // allows you to use T? 
    public abstract class ConsoleReadType<T> where T: struct
    {
        public abstract T Read();
        public abstract T Read(T? min, T? max);
        public virtual T ReadUntilCorrect(Func<T> FunctionToRun, string message = "")
        {
            while (true)
            {
                try
                {
                    return FunctionToRun();
                }
                catch (ConsoleInputException)
                {
                    if (!string.IsNullOrEmpty(message))
                        ConsoleWrite.Error(message);
                }
            }
        }
    }
    

    Is there a way to make these classes static in the end to be used as a helper without having to instanciate them?

    Not really, you cannot inherit from a static class, so you'd have to remove the ConsoleReadType<T> class. You could, however, use a Factory approach:

    public static class ConsoleReader
    {
        public static ConsoleReadType<T> GetReader<T>()
        {
            if (typeof(T) == typeof(double))
            {
                return new ConsoleReadDouble();
            }
            // etc
        }
    }
    

    I'm also interested by any advice you could give me about my code

    In my opinion, you don't need Read(); at all, Read(T? min, T? max); should be enough. Then, ReadUntilCorrect shouldn't receive a Func<T> but instead call Read. You could do with just:

    public abstract class ConsoleReadType<T> where T: struct
    {
        public abstract T Read(T? min = null, T? max = null);
        public virtual T ReadUntilCorrect(T? min = null, T? max = null)
        {
            while (true)
            {
                try
                {
                    return Read(min, max);
                }
                catch (ConsoleInputException ciex)
                {
                    ConsoleWrite.Error(ciex.Message);
                }
            }
        }
    }