【问题标题】:Create generic class that counts average value of numeric T创建计算数字 T 平均值的通用类
【发布时间】:2020-01-18 22:05:10
【问题描述】:

我需要创建一个类

Average<T> 

包含 3 个方法:

  • T average() - 返回 T 值集合的平均值;
  • add(T item) - 将项目添加到 T 值集合中
  • reset() - 将 Average 实例重置为其默认状态

T 必须是数字(int、float、byte 等)

据我了解,我必须创建基础抽象类或接口:

public abstract class NumericBase<T>{}

我的派生类继承的:

public class NumericInt:NumericBase<int>{} 

之后我应该创建我的平均类:

public class Average<T> where T: NumericBase<T>  

但我猜最后一步是错误的,因为我必须在 Average 类中创建方法 add(T item),如果我正确理解了这个任务,它必须像这样工作:

Average<int> av = new Average();
av.add(3);
av.add(4);
int averageValue = av.average();
av.reset();

我整天都在努力解决这个问题,所以我什么也没做。谁能帮我解决这个问题?

【问题讨论】:

  • 数字没有这样的通用约束。我不确定您是否真的需要使用泛型类型,但如果出于学习目的您只想使用泛型和Enumerable 方法,您可以在构造函数中检查T 的类型,如果它不是允许的类型之一类型(例如 int 和 double)然后抛出异常。
  • 我尝试为我需要的每种类型使用派生类并覆盖包含所有添加的 T 项的抽象属性 ValueList,我尝试创建由我的抽象 NumericBase 类的构造函数初始化的 ItemType 属性在 Add 函数中使用它作为参数类型。
  • 拥有抽象基类不会改变第一条评论中提到的内容。仅出于学习目的,作为示例,我分享了一个答案。
  • 谢谢。但任务是这样制定的。在这个任务中使用泛型不是我的决定。
  • @DmitryL 没问题。希望你明白这一点。据我所知,基本上所有的答案都在建议多个课程。因此,请确保您阅读了我的回答中的蓝点:)

标签: c# .net generics


【解决方案1】:

你的约束是错误的,没有办法有T : NumericBase&lt;T&gt;,它会是一个无限递归的类型。

不幸的是,C# 没有针对“数字”类型的通用约束,允许您将 T 的实例添加在一起或将它们除以一个数字。您无法表达“这是一个数字”的约束,但有一种方法可以将 T 约束为 可转换 为一个数字(以及一堆其他类型)。

public class Average<T> where T : IConvertible
{
    public void Add(T item)
    {
        double converted = item.ToDouble(null); 
        ...
    }
}

这样您就可以将自己限制为双精度浮点数,并且任何尝试将该类与自定义类型一起使用的人都必须实现整个IConvertible interface, which is pretty big。我不确定该界面的用途是什么,但到底是什么。

几乎没有其他方法可以做到这一点而不放弃您的要求,即intT 的正确参数。如果我们真的放弃了,那么整个世界的可能性就会打开:

public interface IAverageable<TValue, TAverage>
{
    TValue AddTogether(TValue other);
    TAverage DivideByCount(int count);
}

public struct AverageableInt : IAverageable<AverageableInt, double>
{
    private readonly int _n;

    public AverageableInt(int n) => _n = n;

    public AverageableInt AddTogether(AverageableInt other) =>
        new AverageableInt(this._n + other._n);

    public double DivideByCount(int count) => (double)_n / count;
}

public class Average<TValue, TAverage> where TValue : IAverageable<TValue, TAverage>
{
   ... // Implementation.
}
var average = new Average<AverageableInt, double>();

average.Add(new AverageableInt(3));
average.Add(new AverageableInt(4));
...

您可以通过在intAverageableInt 之间引入用户定义的转换来使其更简洁:

// Inside AverageableInt.
    public static implicit operator AverageableInt(int n) => new AverageableInt(n);
var average = new Average<AverageableInt, double>();

average.Add(3);
average.Add(4);
...

这将需要您添加许多自定义类型以使其适用于每种内置类型,但它是一种有效的、类型安全的解决方案。

【讨论】:

    【解决方案2】:

    考虑以下几点:

    • 对于数字没有这样的通用约束。
    • 当您想到限制为 2-3 个类型的泛型约束时,通常意味着您不需要泛型类型,而是需要创建 2-3 个不同的类。

    我不确定您是否真的需要使用泛型类型,但如果出于学习目的,您只想使用泛型、列表和 Enumerable 类方法,您可以在构造函数中检查 T 的类型,如果是不是允许的类型之一(例如 int 和 double)然后抛出异常。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    public sealed class MyBadClass<T>
    {
        List<T> list;
        public MyBadClass()
        {
            var allowedTypes = new[] { typeof(int), typeof(double), typeof(float) };
            if (!allowedTypes.Contains(typeof(T)))
                throw new Exception($"Type '{typeof(T)}' not supported.");
            list = new List<T>();
        }
        public double Average()
        {
            return list.Cast<double>().Average();
        }
        public void Add(T value)
        {
            list.Add(value);
        }
        public void Reset()
        {
            list.Clear();
        }
    }
    

    【讨论】:

    • 我不同意第二个项目符号,只是因为从概念上讲,您不想将自己限制为 2-3 种类型,而是希望将其限制为满足特定合同的所有类型,即它们可以相加并除以一个数字。它在 C# 中无法在语法上表达,但其他语言确实有数字约束,而且非常有用。
    • @V0ldek 一般来说,我也喜欢有一个数字通用约束或有一些通用约束与OR, ..,但答案是特定于 C#,而 C# 没有允许你有这样的通用约束,我想说第二点在 C# 中是有效的。
    【解决方案3】:

    请注意,将 T 限制为数字类型是不可能的,并且不允许将类命名为与属性 Average 相同。所以我将我的课程命名为Statistics

    您需要一个非泛型类来充当工厂,以简化派生类的实例化。所以你打电话给Statistics.Int();而不是new Statistics&lt;int&gt;.IntStatistics();

    static class Program
    {
        static void Main(string[] args)
        {
            // start integer statistics
            var int_ave = Statistics.Int();
            int_ave.Add(3);
            int_ave.Add(5);
            int_ave.Add(7);
    
            Debug.Assert(int_ave.Count == 3);
            Debug.Assert(int_ave.Average == (3+5+7)/3);
    
            // start float statistics
            var float_ave = Statistics.Float();
            float_ave.AddRange(2f, 4f, 7f, 9f);
            Debug.Assert(float_ave.Count == 4);
            Debug.Assert(float_ave.Average == (2f+4f+7f+9f)/4);
        }
    
    }
    /// <summary>
    /// Factory
    /// </summary>
    public static class Statistics
    {
        public static Statistics<byte> Byte() => new Statistics<byte>.ByteStatistics();
        public static Statistics<int> Int() => new Statistics<int>.IntStatistics();
        public static Statistics<float> Float() => new Statistics<float>.FloatStatistics();
        public static Statistics<double> Double() => new Statistics<double>.DoubleStatistics();
        public static Statistics<decimal> Decimal() => new Statistics<decimal>.DecimalStatistics();
    }
    /// <summary>
    /// Base class
    /// </summary>
    public abstract class Statistics<T> where T : struct, IComparable<T>
    {
        public T Average { get; private set; }
        public int Count { get; private set; }
    
        /// <summary>
        /// When overidden in derived classes the item is considered
        /// and a new average is computed. <see cref="Count"/> is 
        /// also incremented.
        /// </summary>
        /// <param name="item">The numeric value to add.</param>
        public abstract void Add(T item);
    
        /// <summary>
        /// Adds multiple values
        /// </summary>
        public void AddRange(IEnumerable<T> list)
        {
            foreach (var x in list)
            {
                Add(x);
            }
        }
        /// <summary>
        /// Adds multiple values
        /// </summary>
        public void AddRange(params T[] list)
        {
            AddRange(list.AsEnumerable());
        }
        /// <summary>
        /// Resets the statistics.
        /// </summary>
        public void Reset()
        {
            this.Average = default(T);
            this.Count = 0;
        }
    
        /// <summary>
        /// Derived class for byte
        /// </summary>
        internal class ByteStatistics : Statistics<byte>
        {
            public override void Add(byte item)
            {
                Average = (byte)((Count*Average + item)/(Count+1) % 256);
                Count += 1;
            }
        }
        /// <summary>
        /// Derived class for int
        /// </summary>
        internal class IntStatistics : Statistics<int>
        {
            public override void Add(int item)
            {
                Average = (Count*Average + item)/(Count+1);
                Count += 1;
            }
        }
        /// <summary>
        /// Derived class for float
        /// </summary>
        internal class FloatStatistics : Statistics<float>
        {
            public override void Add(float item)
            {
                Average = (Count*Average + item)/(Count+1);
                Count += 1;
            }
        }
        /// <summary>
        /// Derived class for double
        /// </summary>
        internal class DoubleStatistics : Statistics<double>
        {
            public override void Add(double item)
            {
                Average = (Count*Average + item)/(Count+1);
                Count += 1;
            }
        }
        /// <summary>
        /// Derived class for decimal
        /// </summary>
        internal class DecimalStatistics : Statistics<decimal>
        {
            public override void Add(decimal item)
            {
                Average = (Count*Average + item)/(Count+1);
                Count += 1;
            }
        }
    }
    

    实现MaxMin 属性应该相当简单,因为每个T 都必须实现IComparable&lt;T&gt;,它检查哪个值更小或更大。

    【讨论】:

      【解决方案4】:

      您似乎遇到了语法错误。您缺少应该在“where T”语句之后的通用约束。

      而不是这个:public class Average&lt;T&gt; where T: NumericBase&lt;T&gt;, 试试这个:public class Average&lt;T&gt; where T: struct, NumericBase&lt;T&gt;

      那里的结构用作泛型类型的约束。点击此链接了解详情C#: Constraints in Generics

      【讨论】:

      • 存在通用约束 - T 被约束为或派生自 NumericBase&lt;T&gt;。这是一个非常无用的约束,因为没有类型可以真正满足它,但它在语法上是有效的。
      猜你喜欢
      • 2014-08-14
      • 1970-01-01
      • 1970-01-01
      • 2020-02-15
      • 2020-09-27
      • 2012-04-11
      • 2018-07-19
      • 1970-01-01
      • 2013-10-25
      相关资源
      最近更新 更多