如何在C#中对具有抽象内部类型的泛型进行模式匹配?

问题描述

我想应用switch语句模式匹配来确定我是否正在处理特定的泛型类型。内部类型可以是从抽象类继承的几种类型之一。这是一个示例:

using System;

namespace ConsoleApp1
{
  abstract class Vehicle
  {
    public abstract string Name { get; }
  }

  internal class Sedan : Vehicle
  {
    public override string Name => "Sedan";
  }

  abstract class AbstractLoad
  {
    public abstract string Name { get; }
  }

  internal class Oil : AbstractLoad
  {
    public override string Name => "Oil";
  }

  internal class Lumber : AbstractLoad
  {
    public override string Name => "Lumber";
  }

  class Semi<TLoad> : Vehicle where TLoad : AbstractLoad
  {
    public override string Name => "Semi";

    public TLoad Load { get; set; }
  }
  class Program
  {
    static void Main(string[] args)
    {
      var vehicle = new Semi<Oil> {Load = new Oil()};

      DescribeVehicle(vehicle);
    }

    static void DescribeVehicle(Vehicle vehicle)
    {
      switch (vehicle)
      {
        case Sedan sedan:
          Console.WriteLine("This vehicle is a sedan");
          break;
        case Semi<AbstractLoad> semi:
          Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
          break;
        default:
          Console.WriteLine("We don't kNow what kind of vehicle this is.");
          break;
      }
    }
  }
}

我要打印,“这辆车是半载油”。但是,它会打印:“我们不知道这是哪种车辆。”有什么办法可以使它匹配Semi的情况?

解决方法

考虑一下vehicle的类型信息包含哪些内容:vehicle 的确是ConsoleApp1.Semi<ConsoleApp1.Oil>而不是ConsoleApp1.Semi<ConsoleApp1.AbstractLoad>,对吗?这就是为什么类型不匹配的原因。

您在这里有几种可能性:

  1. 您显然可以使用多个case分支,每个分支都具有确切的类型,但这是不好的样式,因为它违背了目的,使您编写了大量重复的代码,并且极有可能忘记添加新的分支创建新的AbstractLoad子类时分支。仅当您确实没有其他选择时,才执行此操作,但是在需要此操作时,它应该使您非常头痛。如果可能的话,避免使用它,这是不好的风格。不,真的。
// === DISCLAIMER ===
// Just for illustrative purposes.
// Don't use this solution,it's bad style.

using System;

namespace ConsoleApp1
{
    abstract class Vehicle
    {
        public abstract string Name { get; }
    }

    internal class Sedan : Vehicle
    {
        public override string Name => "Sedan";
    }

    abstract class AbstractLoad
    {
        public abstract string Name { get; }
    }

    internal class Oil : AbstractLoad
    {
        public override string Name => "Oil";
    }

    internal class Lumber : AbstractLoad
    {
        public override string Name => "Lumber";
    }

    class Semi<TLoad> : Vehicle where TLoad : AbstractLoad
    {
        public override string Name => "Semi";

        public TLoad Load { get; set; }
    }
    class Program
    {
        static void Main(string[] args) {
            var vehicle = new Semi<Oil> { Load = new Oil() };

            DescribeVehicle(vehicle);
        }

        static void DescribeVehicle(Vehicle vehicle) {
            switch (vehicle) {
                case Sedan _:
                    Console.WriteLine("This vehicle is a sedan");
                    break;
                case Semi<Oil> _:
                    Console.WriteLine("This vehicle is a semi carrying Oil.");
                    break;
                case Semi<Lumber> _:
                    Console.WriteLine("This vehicle is a semi carrying Lumber.");
                    break;
                // Add new cases here (but rather don't use this solution at all)
                default:
                    Console.WriteLine("We don't know what kind of vehicle this is.");
                    break;
            }
        }
    }
}
  1. 您可以创建第二个DescribeVehicle(Vehicle vehicle)方法,而不是通过开关使用一个DescribeVehicle<TLoad>(Semi<TLoad> semi) where TLoad : AbstractLoad方法。这是中等程度的代码重复,危险程度较小,但仍然不是很漂亮。
using System;

namespace ConsoleApp1
{
    abstract class Vehicle
    {
        public abstract string Name { get; }
    }

    internal class Sedan : Vehicle
    {
        public override string Name => "Sedan";
    }

    abstract class AbstractLoad
    {
        public abstract string Name { get; }
    }

    internal class Oil : AbstractLoad
    {
        public override string Name => "Oil";
    }

    internal class Lumber : AbstractLoad
    {
        public override string Name => "Lumber";
    }

    class Semi<TLoad> : Vehicle where TLoad : AbstractLoad
    {
        public override string Name => "Semi";

        public TLoad Load { get; set; }
    }
    class Program
    {
        static void Main(string[] args) {
            var vehicle = new Semi<Oil> { Load = new Oil() };

            DescribeVehicle(vehicle);
            Console.ReadKey();
        }

        static void DescribeVehicle(Vehicle vehicle) {
            switch (vehicle) {
                case Sedan _:
                    Console.WriteLine("This vehicle is a sedan");
                    break;
                default:
                    Console.WriteLine("We don't know what kind of vehicle this is.");
                    break;
            }
        }

        static void DescribeVehicle<TLoad>(Semi<TLoad> semi) where TLoad : AbstractLoad {
            Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
        }
    }
}

真正的问题是,使用带有static语句的独立switch函数的方法已经成为问题的一部分:

  1. 由于您已经在使用继承,因此请使用ToString()方法。在SedanSemi<TLoad>类中使用所需的输出来覆盖它,并且理想情况下,对Oil / Lumber等类执行相同的操作。或者,如果您还需要ToString(),则可以在string Describe()Vehicle中创建自己的抽象基础方法AbstractLoad,并在Sedan / {{ 1}}等。
Oil
,

更新

我喜欢LWChris的第二种解决方案

效果很好:

class Program
{
    static void Main(string[] args)
    {
        var vehicle = new Semi<Oil> { Load = new Oil() };
        DescribeVehicle<Oil>(vehicle);
    }

    static void DescribeVehicle<TLoad>(Semi<TLoad> vehicle) where TLoad : AbstractLoad
    {
        switch (vehicle)
        {
            case Sedan _:
                Console.WriteLine("This vehicle is a sedan");
                break;

            case Semi<TLoad> semi:
                Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
                break;

            default:
                Console.WriteLine("We don't know what kind of vehicle this is.");
                break;
        }
    }
}

=== 8

我想,您必须使用AbstractLoad作为内部类型,因为Semi并非与Semi直接相同:

 class Program
    {
        static void Main(string[] args)
        {
            var vehicle = new Semi<AbstractLoad> { Load = new Oil() };

            DescribeVehicle(vehicle);
        }

        static void DescribeVehicle(Vehicle vehicle)
        {
            switch (vehicle)
            {
                case Sedan _:
                    Console.WriteLine("This vehicle is a sedan");
                    break;

                case Semi<AbstractLoad> semi:
                    Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
                    break;

                default:
                    Console.WriteLine("We don't know what kind of vehicle this is.");
                    break;
            }
        }
    }

此版本有效。

,

解决方案2。 (由LWChris建议)

两种方法的版本:

https://dotnetfiddle.net/Widget/Preview?url=/Widget/3yVevr

class Program
{
    static void Main(string[] args)
    {
        var vehicle = new Semi<Oil> { Load = new Oil() };
        DescribeVehicle(vehicle);
        var sedan = new Sedan();
        DescribeVehicle(sedan);
    }

    static void DescribeVehicle(Vehicle vehicle)
    {
        switch (vehicle)
        {
            case Sedan _:
                Console.WriteLine("This vehicle is a sedan");
                break;

            case Semi<AbstractLoad> semi:
                Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
                break;

            default:
                Console.WriteLine("We don't know what kind of vehicle this is.");
                break;
        }
    }

    static void DescribeVehicle<TLoad>(Semi<TLoad> vehicle) where TLoad : AbstractLoad
    {
        switch (vehicle)
        {
            case Semi<TLoad> semi:
                Console.WriteLine($"This vehicle is a semi carrying {semi.Load.Name}");
                break;

            default:
                Console.WriteLine("We don't know what kind of vehicle this is.");
                break;
        }
    }
}