Есть обобщенный класс:
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
.
Возможно ли такое без использования приведений и перегрузок?
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();
}
}
За сравнение отвечает интерфейс 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();
}
}
Для таких типов обычно создают кучу перегрузок. Можно посмотреть, например, метод 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?>();
Ещё один вариант реализации.
Условия: либо тип 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));
Реализация примерно такая:
public bool HasValue(T value)
{
if (value == null)
return false;
return (value >= From) && (To >= value);
}
PS. В первый раз написал вообще не верно.
Получается, что ничего приводить не надо, т.к. числа и DataTime
мы можем сравнивать. И первым делом надо проверить на null
, а потом сравнить обычным способом.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Добрый день, необходимо реализовать телеграм бота c# на паттерне Конечный Автомат (Машина состояний), сталкивался ли кто либо с подобной задачей?...