如何根据 C# 中泛型 class 中的类型以不同方式实现方法?

How to implement methods differently depending on type in a generic class in C#?

我决定尝试制作一个基本的 class 来处理矩阵,以进一步了解该语言。

我想让 class 能够处理不同类型的矩阵(int、double、bool 等)以及非数字对象,例如字符串甚至其他 class 和结构类型如果可能的话)。我不打算实现普通数学矩阵(如矩阵加法和乘法)会得到的所有基本算术,而是我想将矩阵用作以网格模式存储数据的一种方式,有点像你会在 MatLab 中看到什么。到目前为止,这是我的矩阵 class:

class Matrix<T>
{
    private readonly T[,] _elements;
    public readonly int Rows;
    public readonly int Cols;

    public T this[int i, int j]
    {
        get { return _elements[i, j]; }
        set { _elements[i, j] = value; }
    }

    public Matrix(int rows, int cols, T fill) //fill is a baseline default type that should populate the matrix
    {
        Rows = rows;
        Cols = cols;
        _elements = new T[rows, cols];
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                _elements[i, j] = fill;
            }
        }
    }

    public void Display()
    {
        StringBuilder sb = new StringBuilder();
        try //surrounded by try-catch in case a type conversion error occurs
        {
            for (int i = 0; i < Rows; i++)
            {
                sb.AppendLine();
                for (int j = 0; j < Cols; j++)
                {
                    sb.Append(_elements[i, j] + "\t");
                }
            }
            Console.WriteLine(sb.ToString());
        }
        catch
        {
            Console.WriteLine("An error occurred.");
        }
    }
}

所以,在你 bash 我做错所有事情并且不遵守任何代码约定之前,让我们看一下我在 class 中创建的 Display 方法。显然,此方法适用于整数、双精度和字符串等基本类型,因为这些类型可以隐式转换为字符串并显示在控制台上。当我们开始制作接受用户定义的 classes 类型的矩阵时,就会变得棘手。这是一个例子:

假设我有一个简单的人 class:

class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

我想让 Matrix class 的用户能够根据指定的类型以某种方式更改 Display 方法的实现,例如, 以下代码将显示人员姓名矩阵,而不是尝试为每个元素显示类型“人员”:

Matrix<Person> people = new Matrix<Person>(4, 4, new Person("John", 56));
people.Display();

我考虑过可能使用某种接口强制任何用户定义的类型将它们自己隐式转换为字符串,以便 Display 函数可以显示 personObject.Name 而不是尝试显示整个 personObject,但老实说,我不知道那会是什么样子,甚至不知道它是否可能,因为这意味着要让基本类型像 intstring 一样工作他们也必须实现接口,对吗?

对于这样的用法,我会缩小 T 以确保可以显示类型。因此,任何实现都被迫提供显示自身的能力(w/o 覆盖 ToString())

  public abstract class MatrixItemBase
   {
     public abstract string Display();
   }

  public class Matrix<T> where T : MatrixItemBase
   {
    public void Display()
     {
      StringBuilder sb = new StringBuilder();
      try //surrounded by try-catch in case a type conversion error occurs
       {
        for (int i = 0; i < Rows; i++)
         {
            sb.AppendLine();
            for (int j = 0; j < Cols; j++)
            {
                sb.AppendLine(_elements[i, j].Display());
            }
         }
         Console.WriteLine(sb.ToString());
        }
       catch
       {
         Console.WriteLine("An error occurred.");
       }
     }
   }

class Person : MatrixItemBase
{
  public string Name;
  public int Age;

  public Person(string name, int age)
  {
    Name = name;
    Age = age;
  }

  public override string Display()
  {
    return Name; //or whatever
  }
}

或者您可以使用接口而不是抽象基础 class...

如果您还想支持结构和基本类型 w/o 控制输出,您可以删除缩小范围并向显示方法添加条件,例如

//inside your for loop
var e = _elements[i, j];
if (e is MatrixItemBase item)
  sb.AppendLine(item.Display());
else
  sb.AppendLine(e.ToString());
      

回答

最好的解决方案可能是使用抽象 类。

此外,覆盖 object.ToString() 方法而不是创建您自己的 Display() 方法会更好。

using System.Text;

abstract class MatrixItemType
{
    public abstract override string ToString ();
}

class Matrix<T> where T : MatrixItemType
{
    private readonly T[,] _elements;
    public readonly int rows;
    public readonly int cols;

    public Matrix ( int rows , int cols , T fill )
    {
        this.rows = rows;
        this.cols = cols;
        _elements = new T[rows , cols];

        for ( int i = 0; i < rows; i++ )
        {
            for ( int j = 0; j < cols; j++ )
            {
                _elements[i , j] = fill;
            }
        }
    }

    public override string ToString ()
    {
        StringBuilder sb = new StringBuilder();
        try //surrounded by try-catch in case a type conversion error occurs
        {
            for ( int i = 0; i < rows; i++ )
            {
                for ( int j = 0; j < cols; j++ )
                {
                    sb.Append(_elements[i , j].ToString() + "\t");
                }

                sb.AppendLine();
            }

            return sb.ToString();
        }
        catch
        {
            return null;
        }
    }
}

例子

using System;

class Person : MatrixItemType
{
    public string name;
    public int age;

    public Person ( string name , int age )
    {
        this.name = name;
        this.age = age;
    }

    public override string ToString ()
    {
        return $"{name} is {age} years old.";
    }
}

class Program
{
    static void Main ()
    {
        Matrix<Person> people = new Matrix<Person>(4 , 4 , new Person("John" , 56));
        Console.Write(people.ToString());
    }
}

这输出:

John is 56 years old.   John is 56 years old.   John is 56 years old.   John is 56 years old.
John is 56 years old.   John is 56 years old.   John is 56 years old.   John is 56 years old.
John is 56 years old.   John is 56 years old.   John is 56 years old.   John is 56 years old.
John is 56 years old.   John is 56 years old.   John is 56 years old.   John is 56 years old.

额外

您可以改进一些地方 - 大多数是做同样事情的较短代码。