【问题标题】:Сasting a variable to a type without switch caseСasting a variable to an type without switch case
【发布时间】:2019-01-03 01:39:34
【问题描述】:

我有一个对象列表ListOfObjects,它们都是相同类型,但具体类型未知(但是,我知道所有可能的类型)。有 许多可能的类型。每个对象都有属性Name,它是其类型的字符串。我想做如下的事情:

foreach (object elements in ListOfObjects)
{
    // Some code here that casts elements into the specific type and pass it into another function
}

我知道一种方法是使用 switch case 语句

  switch (ListOfObjects[0].Name)
  {
      case "Type1":
          //cast into Type1 and pass into function
      case "Type2":
          //cast into Type2 and pass into function
      default:
          //something
          break;
  }

有没有更清洁的方法来做到这一点?是否可以将可能的类型存储在字典中并从该字典中转换?

【问题讨论】:

标签: c#


【解决方案1】:

模式匹配

首先我想介绍在 switch 语句中使用模式匹配来处理不同的类型,如下所示:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

示例取自 Pattern Matching - C# Guide


输入字典

除此之外,是的,你可以写一本字典……问题在于项目的类型。

我们可以这样做:

Dictionary<Type, Action<object>> dictionary;

// (initialize and populate somewhere else) ...

if (dictionary.TryGetValue(element.GetType(), out var action))
{
    action(element);
}

但是,在这里你必须使用Action&lt;object&gt;,因为我们需要为项目指定类型(不,我们不能说Action&lt;?&gt; - 好吧,我们可以使用Action&lt;dynamic&gt;,但你不能转换@987654329 @ to Action&lt;dynamic&gt;),强制你在被调用的方法中进行强制转换。

我们可以争辩说,强制转换是一种告诉编译器我们知道一些它不知道的东西的方法。在这种情况下,我们知道 那个 对象实际上是给定类型的。

我们可以做得更好/更差,这取决于你如何看待它......

Dictionary<Type, Delegate> dictionary;

// (initialize and populate somewhere else) ...

if (dictionary.TryGetValue(element.GetType(), out var @delegate))
{
    @delegate.DynamicInvoke(element);
}

这实际上是后期绑定。我们在编译时不知道类型......作为开发人员,您必须确保提供正确类型的委托。但是,如果我们已经强制执行编译器不知道的知识,那么这是可以接受的。

我们可以制作一个辅助方法来使它更容易:

void SetMethod<T>(Action<T> action)
{
    dictionary[typeof(T)] = action;
}

这里编译器可以检查方法的类型是否正确。然而,从编译器的角度来看,当您使用字典时,此信息会丢失(不可用)。如果你愿意,这是一种类型擦除。


动态

现在,如果我们放弃类型,我们可以在 answer 之后使用 dynamic TheGeneral


附录:调用已知方法(使用 MethodInfo)

您可以通过名称调用方法,例如,如果您有以下内容:

class Helper
{
    public static void Method(T input)
    {
        Console.WriteLine(input.GetType());
    }
}

你可以这样做:

var methodInfo = typeof(Helper).GetMethod("Method");

// ...

methodInfo.Invoke(null, new object[]{element});

然后您可以将所有方法放入一个帮助类中,并通过名称(您可以从类型的名称派生)找到它们。


如果要调用具有泛型参数的已知方法,可以使用 MethodInfo。我们需要知道方法是否是静态的,以及泛型参数是否是方法定义或声明类型定义的一部分...

一方面,如果你有这样的事情:

class Helper<T>
{
    public static void Method(T input)
    {
        Console.WriteLine(input.GetType());
    }
}

你可以这样做:

var helperType = typeof(Helper<>);

// ...

var specificMethodInfo = helperType.MakeGenericType(element.GetType()).GetMethod("Method");
specificMethodInfo.Invoke(null, new object[]{element});

另一方面,如果你有这个:

class Helper
{
    public static void Method<T>(T input)
    {
        Console.WriteLine(input.GetType());
    }
}

你可以这样做:

var methodInfo = typeof(Helper).GetMethod("Method");

// ...

var specificMethodInfo = methodInfo.MakeGenericMethod(element.GetType());
specificMethodInfo.Invoke(null, new object[]{element});

注意:我将null 作为第一个参数传递给调用。这就是我调用该方法的实例。没有,因为它们是静态的。如果不是,那么您需要一个实例...例如,您可以尝试使用 Activator.CreateInstance 创建一个。


附录:查找要调用的内容(类型发现)

也许你有不同的方法要调用(它们不一样,但有不同的泛型参数),但你不想有手动填充字典的麻烦。

这就是类型发现的用武之地。

首先,我建议使用一个属性,例如:

[AttributeUsage(AttributeTargets.Method)]
public sealed class DataHandlerAttribute : Attribute { }

然后我们需要一个我们将要搜索的类型的列表。如果我们要搜索一个已知的程序集,我们可以这样做:

var assembly = typeof(KnownType).GetTypeInfo().Assembly;
var types = assembly.GetTypes();

注意:如果您的目标平台不支持此功能(.NET Standard 1.0 到 1.4),您将不得不手动编写类型列表。

接下来,我们需要一个谓词来检查给定类型是否是我们感兴趣的类型之一:

bool IsDataHandlerMethod(MethodInfo methodInfo)
{
    var dataHandlerAttributes = return (DataHandlerAttribute[])item.GetCustomAttributes(typeof(DataHandlerAttribute), true);
    if (attributes == null || attributes.Length == 0)
    {
        return false;
    }
    if (methodInfo.DeclaringType != null)
    {
        return false;
    }
    if (methodInfo.ReturnTpye != typeof(void))
    {
        return false;
    }
    var parameters = methodInfo.GetParameters();
    if (parameters.Length != 1)
    {
        return false;
    }
    if (paramters[0].IsByRef || paramters[0].IsOut)
    {
        return false;
    }
    return true;
}

以及将它们转换为委托的方法:

(Type, Delegate) GetTypeDelegatePair(MethodInfo methodInfo)
{
    var parameters = methodInfo.GetParameters();
    var parameterType = parameters[0].ParameterType;
    var parameterTypeArray = new []{parameterType};
    var delegateType = typeof(Action<>).MakeGenericType(parameterTypeArray);
    var target = null;
    if (!methodInfo.IsStatic)
    {
        var declaringType = methodInfo.DeclaringType;
        target = instance = Activator.CreateInstance(declaringType);
    }
    return (parameterType, methodInfo.CreateDelegate(delegateType, target));
}

现在我们可以这样做了:

var dataHandlers = types
            .SelectMany(t => t.GetTypeInfo().GetMethods())
            .Where(IsDataHandlerMethod)
            .Select(GetTypeDelegatePair);

我们将有一对可枚举的类型和委托,我们可以使用它们来填充我们的字典。

注意:上面的代码仍然需要做一些工作(例如,我们可以只调用一次GetParameters 吗?),并假定一个现代.NET 目标(需要额外的工作才能使其工作)在旧平台上)。另请注意,我提供的类型发现代码不处理泛型方法,您可以检查 Type.IsGenericTypeDefinitionMethodInfo.IsGenericMethodDefinition... 但是,我建议避免使用它们。实际上,对于要将所有方法放在单个静态类中的情况,应该很容易修改。例如,您也可以使用类似的方法来获取工厂方法。

【讨论】:

    【解决方案2】:

    如果你有重载,并且你不想想使用switch,你可以使用dynamic,但是你真的需要问问自己这是否是一个设计问题,并且应该以更合适的方式解决。即,为什么您仍然需要将不相关的类型存储在列表中。

    public static void Test(Version version)
    {
       Console.WriteLine("is a version");
    }
    public static void Test(FormatException formatException)
    {
       Console.WriteLine("is a formatException");
    }
    static void Main(string[] args)
    {
    
       var list = new List<object>();
       list.Add(new Version());
       list.Add(new FormatException());
    
       foreach (var item in list)
          Test((dynamic)item);  
    }
    

    输出

    is a version
    is a formatException
    

    Full Demo Here

    注意:如果找不到过载,这将全部中断。咔嚓!所以我不建议使用它,除非你真的真的需要

    【讨论】:

      【解决方案3】:

      您实际上可以使用标准的系统属性和方法来实现您的目标。

      首先要做的是获取Type

      var type = System.Type.GetType(elements.Name, false, true);
      

      false 参数表示你不想在出错时抛出异常,true 参数表示你想忽略大小写。

      一旦你有了一个有效的类型,你就可以调用System.Activator来创建一个新的类实例:

      if (type != null) {
         var classInstance = System.ServiceActivator.CreateInstance(type);
         // Use the instance here
      }
      

      请注意,CreateInstance 的这种重载需要一个无参数的公共构造函数。但是,还有其他 overloads 允许您传递参数和访问非公共构造函数。

      【讨论】:

        【解决方案4】:

        您可以使用Type.GetType 方法来获取对象的类型,而不是进行字符串比较。这是相同的代码:

        foreach (var element in ListOfObjects)
        {
            var type = Type.GetType(element.Name);
            if (type == typeof(YOUR_OBJECT_TYPE))
            {
                   // Do Something
            }
        
        }
        

        阅读更多关于GetTypehere

        【讨论】:

          【解决方案5】:

          我不确定我是否清楚地理解了您的问题,但是, 也许它可以帮助你,我认为你不需要在名称字段中输入,因为你可以像这样得到类型。而且我也不明白你为什么要再次将这种类型转换为自身。

          foreach (var element in ListOfObjects)
          {
          var _type = element.getType()
          }
          

          您可以只使用 switch case 或 if 语句来制作路由。

          【讨论】:

            【解决方案6】:

            肯定使用字典映射类型和方法是可以的:

            Dictionary<Type, Action<Object>> methodMap = new Dictionary<Type, Action<Object>>();
            

            预加载字典:

            static void Action1(Object obj)
            {
              //do necessary casting here or not
              Console.WriteLine("i handle Type1");
            }
            static void Action2(Object obj)
            {
               Console.WriteLine("i handle Type2");
            }
            Dictionary<Type, Action<Object>> methodMap = new Dictionary<Type, Action<Object>>();
            methodMap[typeof(Type1)] = Action1;
            methodMap[typeof(Type2)] = Action2;
            

            并使用字典:

            List<Object> collector = new List<Object>();
            collector.Add(new Type1());
            collector.Add(new Type2());
            
            methodMap[collector[0].GetType()](collector[0]);
            methodMap[collector[1].GetType()](collector[1]);
            

            类型方法映射也适用于那些古老的不同的类。这将是您选择这种方法而不是重载或虚拟成员函数的关键因素。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2022-12-01
              • 2023-02-08
              • 2022-12-02
              • 2022-12-27
              • 2022-12-02
              • 2022-12-27
              • 1970-01-01
              • 2022-12-02
              相关资源
              最近更新 更多