【问题标题】:Writing a generic class that is not a container? [C#]编写一个不是容器的泛型类? [C#]
【发布时间】:2014-11-02 12:51:46
【问题描述】:

目标:

我一直在尝试用 C# 编写一个通用的帮助程序/实用程序类,它表示经过边界检查的数字范围。

这个想法是,用户可以通过定义最小值和最大值(例如,从 0 到 10、从 -15 到 3、从 -7 到 -2 等)来创建一个连续的数字范围。该范围还包含一个当前值,并根据上限/下限检查该值。如果当前值大于最大值(或小于最小值),则它要么被封顶,要么被环绕。我希望这个类是通用的,以便客户可以为他们的用例选择合适的类型/大小/精度。

这是我目前所拥有的:

public class Range<T> where T : IComparable<T> {

    private T value;
    private T min;
    private T max;

    private bool wrap;

    public Range ( T minimumValue, T maximumValue, T initialValue, bool tempWrap = false ) {
        value = initialValue;
        min = minimumValue;
        max = maximumValue;
        wrap = tempWrap;

        LimitBounds ();
    }

    private void LimitBounds () {
        if( wrap != true ) {
            if ( value > max ) { value = max; }
            else if ( value < min ) { value = min ; }
        }
        else {
            T wrapAmount = 0;

            if ( value > max ) { 
                wrapAmount = value - max;
                value = min + wrapAmount;
            }
            else if ( value < min ) {
                wrapAmount = min - value;
                value = max - wrapAmount;
            }
        }
    }

    public T GetValue() {
        return value;
    }

    public void SetValue(T tempValue) {
        value = tempValue;
        LimitBounds ();
    }

    public void Increment() {
        value++;
        LimitBounds ();
    }

    public void Decrement() {
        value--;
        LimitBounds ();
    }

    public bool Equals(Range<T> tempRange)
    {
        return (value == tempRange.value);
    }

    public static bool operator > (Range<T> r1, Range<T> r2)
    {
        return (r1.value > r2.value);
    }

    public static bool operator < (Range<T> r1, Range<T> r2)
    {
        return (r1.value < r2.value);
    }

    public static Range<T> operator ++ ( Range<T> r ) {
        r.Increment ();
        return r;
    }

    public static Range<T> operator -- ( Range<T> r ) {
        r.Decrement ();
        return r;
    }
}

问题:

我真的是 C# 的新手,尽管有更多的 C++ 经验,但我还是有点卡在这里。我的编译器在抱怨:

"Operator `>' cannot be applied to operands of type `T' and `T'"

.. 我的大多数运算符重载都有类似的错误。我认为,如果我确保我的类处理的类型实现“可比较”,那么重载比较运算符就可以了。我显然在这里做错了什么,但我不确定我需要做什么。

如果它简化了事情,这个类真的只需要处理纯数值/类型(字节、短、整数、浮点等)..

更新编辑 1:

如下所示,我可以通过在这些函数中切换到 CompareTo 来消除比较操作导致的一堆编译错误:

    public bool Equals(Range<T> tempRange)
    {
        return (value.CompareTo(tempRange.value) == 0);
    }

    public static bool operator > (Range<T> r1, Range<T> r2)
    {
        return (r1.value.CompareTo (r2.value) > 0);
    }

    public static bool operator < (Range<T> r1, Range<T> r2)
    {
        return (r1.value.CompareTo (r2.value) < 0);
    }

我还添加了“结构”约束。不过,我仍然不确定从这里去哪里。由于我一直在使用各种加法、减法、递增和递减运算符,我的代码仍然无法编译。我尝试添加 byte、short、int 和 float 作为额外的类型约束,但这没有奏效,并且给了我一个编译时错误。

从这里我有什么选择?因为这个类只用于处理数字范围(基本上只有各种大小的整数 [byte, short, int, long 也许] 和浮点小数)并且不能为空或引用类型,所以还有其他一些我可以的约束吗用来做这个工作?我是否必须围绕实现自定义“INumber”接口的基本数据类型创建一堆包装类?

或者这是对泛型类的滥用,我应该为不同类型创建一堆独立的范围类?是否可以创建一个实现我想要的行为类型的泛型类?

【问题讨论】:

  • 运算符不是虚拟的,因此永远不能与泛型类型一起使用。使用为受约束类型定义的适用方法。请记住,泛型是 not 模板,并且不会发生“文本替换”。因此,对于泛型类本身中由T 表示的所有 类型,代码必须是类型有效的。
  • 不知道类型的情况下,IncrementDecrement方法中的value++value--怎么办?
  • 呃!我明白了。。谢谢大家。最初我是用'Short's 来写这门课的,但在我开始工作后,我决定让它更通用更有用/更灵活。我考虑过制作一些类,如“IntRange”、“ShortRange”、“FloatRange”等。我认为当我弄清楚时这会好得多。我以前使用过其他人的泛型,但这是我第一次尝试写一个。 :]

标签: c# generics comparison operator-overloading operators


【解决方案1】:

错误的原因是编译器无法确定T的类型是否会正确地重载所有这些运算符。而且也没有这样的约束来强制它。

例如,使用CompareTo 方法代替比较运算符,而不是

value > max

使用

value.CompareTo(max) > 0

对于相等 value.CompareTo(max) == 0 对于小于:value.CompareTo(max) &lt; 0

有关更多信息,请查看文档IComparable.CompareTo Method

IncrementDecrement 运算符比较棘手。您可以为您的类型编写不同的重载来增加和减少,并为当前类型调用适当的重载。

最后一点,如果您打算将此方法仅用于 (byte,int,double) 之类的结构类型,您可能需要添加 struct 约束。

【讨论】:

  • 我认为value.CompareTo(max) &gt; 1 应该是&gt; 0
  • 当然,如果T 是引用类型而value 是空引用,它可能会崩溃。另一个解决方案是if (Comparer&lt;T&gt;.Default.Compare(value, max) &gt; 0) { ... }。另一方面,如果您知道不会将其用于引用类型,则可以将常量指定为where T : struct, IComparable&lt;T&gt;。关于平等,请考虑Equals 是否不是您想要的方法。如果您限制为IEquatable&lt;T&gt;,您将在某些情况下避免装箱和类型检查(尽管它们非常快)。
  • 感谢大家的帮助。添加约束并在适用的情况下切换到 CompareTo 后,我设法使通用比较起作用!不幸的是,我仍然有一堆引发编译错误的加法、减法、递增和递减操作。我还添加了一个结构约束。是否有不同的通用约束可用于帮助使 +、-、++ 和 -- 正常工作?
  • 或者尝试使用泛型来处理这种事情是不可能/不可取的?我应该放弃并只做一些特定的 Range 课程吗? (ByteRange、IntRange、FloatRange 等).. 或者会为我希望此类处理工作的每种类型使用特定约束? (Byte、Short、Int、Float、Double 等作为约束)..
  • @MrKatSwordfish 他们应该有参数。比如void Increment(ref int i),你需要检查 typeof T 并将其传递给适当的函数
【解决方案2】:

这是使用操作符和泛型的问题,很多代码无法编译。例如,value++value--。编译器不知道T 是否实现了特定的运算符。不幸的是,运营商(目前)还没有任何限制,因此您只能使用一些非常非通用的解决方案,或者您可以使用dynamic 作为解决方法。

请注意这是绕过编译时检查。您的代码可以很好地编译在运行时会中断的类型。

public void Increment()
{
    dynamic x = value;
    x++;
    value = x;
}

【讨论】:

  • 谢谢。如果可能的话,我想避免这种情况;尽可能减少运行时错误的风险似乎是个好主意。我宁愿让我的代码在编译时失败,也不愿意外崩溃! :] 我发现这比我想象的要困难得多.. 哈哈
  • @MrKatSwordfish 明白了。当我在项目中完成此操作时,我使用了 struct 约束和明确的 cmets,以最大限度地减少误用的机会。
【解决方案3】:

使用泛型,您不能进行算术运算,因为直到编译时,您才知道 . 的类型。

所以你可以做两件事:

  1. 如果总是这样,您将拥有 long、decimal 等类型,然后将泛型类限制为仅用于这些类型。

  2. 运算符重载

【讨论】:

  • “可以做..运算符重载”吗?
猜你喜欢
  • 2011-02-22
  • 2023-03-29
  • 2010-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-13
相关资源
最近更新 更多