将变量设置为不带开关大小写的类型

时间:2019-01-03 01:39:35

标签: c#

我有一个对象列表ListOfObjects,它们都是同一类型,但是具体类型未知(但是,我确实知道所有可能的类型)。有  许多可能的类型。每个对象都有名称属性,该属性是其类型的字符串。我要执行以下操作:

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;
  }

是否有一种更清洁的方法?是否可以将可能的类型存储在字典中并从该字典中进行投射?

6 个答案:

答案 0 :(得分:3)

如果过载,并且不想使用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

注意:如果找不到重载,这一切都会中断。 -!因此,我不建议您使用它,除非您真的非常需要

答案 1 :(得分:3)


模式匹配

首先,我想介绍在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<object>,因为我们需要为项目指定类型(不,我们不能说Action<?>-好吧,我们可以做Action<dynamic>但是您无法将Action<someType>强制转换为Action<dynamic>),从而迫使您强制转换被调用的方法。

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

根据您的看法,我们可以做得更好/更糟...

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后面紧跟TheGeneraldynamic


附录:调用已知方法(使用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 :(得分:0)

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

要做的第一件事是获取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允许您传递参数并访问非公共构造函数。

答案 3 :(得分:0)

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

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

}

详细了解GetType here

答案 4 :(得分:0)

我不确定我是否清楚地理解了您的问题,但是, 也许它可以为您提供帮助,我认为您无需在name字段中输入内容,因为您可以像这样获得输入内容。而且我也不知道为什么要再次将这种类型强制转换为自身。

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

,您可以只使用switch case或if语句进行路由。

答案 5 :(得分:0)

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

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]);

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