Страницы

Поиск по вопросам

среда, 11 декабря 2019 г.

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

#c_sharp


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

public struct Range
{
    public T From { get; set; }
    public T To { get; set; }

    public bool HasValue(T value)
    {
        // Нужно написать проверку входа в диапазон
    }
}


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

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

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


Ответы

Ответ 1



http://ideone.com/WBbU8d using System; using System.Collections.Generic; public struct Range { public T From { get; set; } public T To { get; set; } public bool HasValue(T value) { return value != null && (From == null || Comparer.Default.Compare(From, value) <= 0) && (To == null || Comparer.Default.Compare(To, value) >= 0); } } И проверка: public class Test { public static void Main() { var a = new Range { From = 10, To = 16 }; Console.WriteLine("var a = new Range { 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 { From = 10, To = 16 }; Console.WriteLine("var b = new Range { 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 { From = null, To = 16 }; Console.WriteLine("var c = new Range { 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 { From = 10, To = null }; Console.WriteLine("var d = new Range { 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 { From = null, To = null }; Console.WriteLine("var e = new Range { 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(); } }

Ответ 2



За сравнение отвечает интерфейс IComparable, можете ограничить типы им. Правда тогда Вы не сможете передавать в качестве T типы Nullable. Можно будет только сравнивать их с From и To через перегрузку HasValue(T? value). Лучшего решения мне в голову не приходит. public struct Range 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 { 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) { return t; } private T GetValue(T? t) where T : struct { return t.Value; } } class Program { static void Main(string[] args) { Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(10)); //true Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(-100)); //false Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(100)); //false Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(10)); //true Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(-100)); //false Console.WriteLine(new Range() { From = -20, To = 20 }.HasValue(100)); //false Console.WriteLine(new Range() { From = null, To = 20 }.HasValue(10)); //true Console.WriteLine(new Range() { From = -20, To = null }.HasValue(10)); //true Console.WriteLine(new Range() { From = -20, To = null }.HasValue(-100)); //false Console.WriteLine(new Range() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-2))); //true Console.WriteLine(new Range() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-10))); //false Console.WriteLine(new Range() { From = DateTime.Now.AddDays(-5), To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(10))); //false Console.WriteLine(new Range() { From = null, To = DateTime.Now.AddDays(5) }.HasValue(DateTime.Now.AddDays(-2))); //true Console.WriteLine(new Range() { From = DateTime.Now.AddDays(-5), To = null }.HasValue(DateTime.Now.AddDays(-2))); //true Console.WriteLine(new Range() { From = DateTime.Now.AddDays(-5), To = null }.HasValue(DateTime.Now.AddDays(-100))); //false Console.ReadLine(); } }

Ответ 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(); var range2 = new Range();

Ответ 4



Ещё один вариант реализации. Условия: либо тип T не Nullable, и реализует IComparable<Т>, либо он есть тип T1?, и уж T1 реализует IComparable<Т1>. К сожалению, не проверяется в compile time. class Range { // компаратор для не-nullable-случая static int CompareNonNullable(V l, V r) where V : IComparable => l.CompareTo(r); // компаратор для nullable-случая static int CompareNullable(V? l, V? r) where V : struct, IComparable { 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).GetMethod( methodName, BindingFlags.Static | BindingFlags.NonPublic); var genericMI = rawMI.MakeGenericMethod(underlying); comparer = (Func)Delegate.CreateDelegate( typeof(Func), genericMI); } static Func 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(0, 5); Console.WriteLine(intRange.Contains(5)); var dtRange = new Range(DateTime.Now.AddDays(-1), null); Console.WriteLine(dtRange.Contains(DateTime.Now));

Ответ 5



Можно сделать через extension: public static class RangeExtensions { public static bool HasValue(this Range range, T value, IComparer comparer = null) where T : struct { comparer = comparer ?? Comparer.Default; return comparer.Compare(value, range.From) >= 0 && comparer.Compare(value, range.To) <= 0; } public static bool HasValue(this Range range, T? value, IComparer comparer = null) where T : struct { comparer = comparer ?? Comparer.Default; if (value == null || range.From == null || range.To == null) { return false; } return comparer.Compare(value.Value, range.From.Value) >= 0 && comparer.Compare(value.Value, range.To.Value) <= 0; } } Потенциально проблема с производительностью этого метода если Range структура - структура будет передаваться по значению. Если Range будет классом или операция не частая, то вот так попробуйте.

Ответ 6



Реализация примерно такая: public bool HasValue(T value) { if (value == null) return false; return (value >= From) && (To >= value); } PS. В первый раз написал вообще не верно. Получается, что ничего приводить не надо, т.к. числа и DataTime мы можем сравнивать. И первым делом надо проверить на null, а потом сравнить обычным способом.

Комментариев нет:

Отправить комментарий