Сделать Generic метод

316
04 июля 2017, 20:04

Есть обобщенный класс:

public struct Range<T>
{
    public T From { get; set; }
    public T To { get; set; }
    public bool HasValue(T value)
    {
        // Нужно написать проверку входа в диапазон
    }
}

T - может быть только числом (int, double) или датой (DateTime) и может быть Nullable.

Нужно реализовать метод HasValue, который будет проверять входит ли value в диапазон от From до To.

Возможно ли такое без использования приведений и перегрузок?

Answer 1

http://ideone.com/WBbU8d

using System;
using System.Collections.Generic;
public struct Range<T>
{
    public T From { get; set; }
    public T To { get; set; }
    public bool HasValue(T value)
    {
        return value != null
            && (From == null || Comparer<T>.Default.Compare(From, value) <= 0)
            && (To == null || Comparer<T>.Default.Compare(To, value) >= 0);
    }
}

И проверка:

public class Test
{
    public static void Main()
    {
        var a = new Range <int> { From = 10, To = 16 };
        Console.WriteLine("var a = new Range <int> { From = 10, To = 16 };");
        Console.WriteLine(a.HasValue(0));
        Console.WriteLine(a.HasValue(15));
        Console.WriteLine(a.HasValue(17));
        Console.WriteLine();
        var b = new Range <int?> { From = 10, To = 16 };
        Console.WriteLine("var b = new Range <int?> { From = 10, To = 16 };");
        Console.WriteLine(b.HasValue(null));
        Console.WriteLine(b.HasValue(0));
        Console.WriteLine(b.HasValue(15));
        Console.WriteLine(b.HasValue(17));
        Console.WriteLine();
        var c = new Range <int?> { From = null, To = 16 };
        Console.WriteLine("var c = new Range <int?> { From = null, To = 16 };");
        Console.WriteLine(c.HasValue(null));
        Console.WriteLine(c.HasValue(0));
        Console.WriteLine(c.HasValue(15));
        Console.WriteLine(c.HasValue(17));
        Console.WriteLine();
        var d = new Range <int?> { From = 10, To = null };
        Console.WriteLine("var d = new Range <int?> { From = 10, To = null };");
        Console.WriteLine(d.HasValue(null));
        Console.WriteLine(d.HasValue(0));
        Console.WriteLine(d.HasValue(15));
        Console.WriteLine(d.HasValue(17));
        Console.WriteLine();
        var e = new Range <int?> { From = null, To = null };
        Console.WriteLine("var e = new Range <int?> { From = null, To = null };");
        Console.WriteLine(e.HasValue(null));
        Console.WriteLine(e.HasValue(0));
        Console.WriteLine(e.HasValue(15));
        Console.WriteLine(e.HasValue(17));
        Console.WriteLine();
    }
}
Answer 2

За сравнение отвечает интерфейс IComparable, можете ограничить типы им. Правда тогда Вы не сможете передавать в качестве T типы Nullable. Можно будет только сравнивать их с From и To через перегрузку HasValue(T? value). Лучшего решения мне в голову не приходит.

public struct Range<T> where T : IComparable
{
    public T From
    {
        get;
        set;
    }
    public T To
    {
        get;
        set;
    }
    public bool HasValue(T value)
    {
        return value.CompareTo(From) >= 0 && value.CompareTo(To) <= 0;
    }
    public bool HasValue(T? value)
    {
        return value.HasValue && value.Value.CompareTo(From) >= 0 && value.Value.CompareTo(To) <= 0;
    }
}

UPD Класс с Nullable проверкой (не проверялся, работоспособность не гарантирую)

public struct Range<T> 
{
    public T From
    {
        get;
        set;
    }
    public T To
    {
        get;
        set;
    }
    public bool HasValue(T value) 
    {
        IComparable _from = null;
        IComparable _to = null;
        IComparable _value = null;
        if (From != null) 
        {
            _from = GetValue(From) as IComparable;
        }
        if (To != null)
        {
            _to = GetValue(To) as IComparable;
        }
        if (value != null)
        {
            _value = GetValue(value) as IComparable;
        }
        if (_value == null) throw new ArgumentNullException();
        bool checkFrom = false;
        if (_from == null || _from != null && _value.CompareTo(_from) >= 0) checkFrom = true;
        bool checkTo = false;
        if (_to == null || _to != null && _value.CompareTo(_to) <= 0) checkTo = true;
        return checkFrom && checkTo;
    }
    private T GetValue<T>(T t) { return t; }
    private T GetValue<T>(T? t) where T : struct { return t.Value; }
}
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Range<int>() { From = -20, To = 20 }.HasValue(10)); //true
        Console.WriteLine(new Range<int>() { From = -20, To = 20 }.HasValue(-100)); //false
        Console.WriteLine(new Range<int>() { From = -20, To = 20 }.HasValue(100)); //false
        Console.WriteLine(new Range<int?>() { From = -20, To = 20 }.HasValue(10)); //true
        Console.WriteLine(new Range<int?>() { From = -20, To = 20 }.HasValue(-100)); //false
        Console.WriteLine(new Range<int?>() { From = -20, To = 20 }.HasValue(100)); //false
        Console.WriteLine(new Range<int?>() { From = null, To = 20 }.HasValue(10)); //true
        Console.WriteLine(new Range<int?>() { From = -20, To = null }.HasValue(10)); //true
        Console.WriteLine(new Range<int?>() { From = -20, To = null }.HasValue(-100)); //false
        Console.WriteLine(new Range<DateTime>() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-2))); //true
        Console.WriteLine(new Range<DateTime>() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-10))); //false
        Console.WriteLine(new Range<DateTime>() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(10))); //false
        Console.WriteLine(new Range<DateTime?>() { From = null, To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-2))); //true
        Console.WriteLine(new Range<DateTime?>() { From = DateTime.Now.AddDays(-5), To = null }.HasValue(DateTime.Now.AddDays(-2))); //true
        Console.WriteLine(new Range<DateTime?>() { From = DateTime.Now.AddDays(-5), To = null }.HasValue(DateTime.Now.AddDays(-100))); //false
        Console.ReadLine();
    }
}
Answer 3

Для таких типов обычно создают кучу перегрузок. Можно посмотреть, например, метод Console.WriteLine. Можно поступить так же: создать множество структур/классов для работы с конкретным типом. Чтобы не писать все их вручную можно применить кодогенерацию T4.

В файле tt пишем:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
using System;
namespace TestApp
{
    <# 
    var types = new string[] { "SByte", "Byte", "Char", "Int16", "UInt16", "Int32", "UInt32",
        "Int64", "UInt64", "Single", "Double", "Decimal", "DateTime", "DateTimeOffset" };
    foreach(var T in types) {
    #>
    public struct Range<#=T#>
    {
        public <#=T#> From { get; set; }
        public <#=T#> To { get; set; }
        public bool HasValue(<#=T#> value)
        {
            return value >= From && value <= To;
        }
    }
    public struct RangeNullable<#=T#>
    {
        public <#=T#>? From { get; set; }
        public <#=T#>? To { get; set; }
        public bool HasValue(<#=T#>? value)
        {
            return value >= From && value <= To;
        }
    }
    <#
        }
    #>
}

Типы добавить/убавить по желанию.
После компиляции будет сгенерирован файл cs со множеством структур.

Далее нужно будет создавать и использовать экземпляры структур явно указанных типов:

var range = new RangeInt32();
var range2 = new RangeNullableDateTime();

В первоначальном варианте это было бы:

var range = new Range<int>();
var range2 = new Range<DateTime?>();
Answer 4

Ещё один вариант реализации.

Условия: либо тип T не Nullable, и реализует IComparable<Т>, либо он есть тип T1?, и уж T1 реализует IComparable<Т1>. К сожалению, не проверяется в compile time.

class Range<T>
{
    // компаратор для не-nullable-случая
    static int CompareNonNullable<V>(V l, V r) where V : IComparable<V> => l.CompareTo(r);
    // компаратор для nullable-случая
    static int CompareNullable<V>(V? l, V? r) where V : struct, IComparable<V>
    {
        if (l == null)
            return r == null ? 0 : 1;
        else if (r == null)
            return -1;
        else return l.Value.CompareTo(r.Value);
    }
    static Range()
    {
        Type parameter = typeof(T);
        Type nullableBase = Nullable.GetUnderlyingType(typeof(T));
        Type underlying = nullableBase ?? parameter; // T или T1
        // проверка на IComparable
        var icomparable = typeof(IComparable<>).MakeGenericType(underlying);
        var hasInterface = underlying.GetInterfaces().Contains(icomparable);
        if (!hasInterface)
            throw new ArgumentException(
                "Type should implement IComparable generic interface");
        var methodName = (nullableBase == null) ? nameof(CompareNonNullable) :
                                                  nameof(CompareNullable);
        var rawMI = typeof(Range<T>).GetMethod(
                        methodName,
                        BindingFlags.Static | BindingFlags.NonPublic);
        var genericMI = rawMI.MakeGenericMethod(underlying);
        comparer = (Func<T, T, int>)Delegate.CreateDelegate(
                                        typeof(Func<T, T, int>), genericMI);
    }
    static Func<T, T, int> comparer;
    public Range(T from, T to)
    {
        From = from;
        To = to;
    }
    public bool Contains(T t) => comparer(From, t) <= 0 && comparer(t, To) <= 0;
    public T From { get; }
    public T To { get; }
}

Пользоваться так:

var intRange = new Range<int>(0, 5);
Console.WriteLine(intRange.Contains(5));
var dtRange = new Range<DateTime?>(DateTime.Now.AddDays(-1), null);
Console.WriteLine(dtRange.Contains(DateTime.Now));
Answer 5

Реализация примерно такая:

public bool HasValue(T value)
{
    if (value == null)
        return false;
    return (value >= From) && (To >= value);
}

PS. В первый раз написал вообще не верно. Получается, что ничего приводить не надо, т.к. числа и DataTime мы можем сравнивать. И первым делом надо проверить на null, а потом сравнить обычным способом.

READ ALSO
Работа со спрайтами

Работа со спрайтами

Как взять Sprite из Spritesheet через ResourcesLoad(path); язык c#

244
Telegram Bot State Machine

Telegram Bot State Machine

Добрый день, необходимо реализовать телеграм бота c# на паттерне Конечный Автомат (Машина состояний), сталкивался ли кто либо с подобной задачей?...

1006
Quartz.Net IoC Unity DI

Quartz.Net IoC Unity DI

Есть класс IJob

512