【问题标题】:C# Casting to Generic typeC# 转换为泛型类型
【发布时间】:2015-11-17 13:24:55
【问题描述】:

我编写了这些类来组成一个复合体,它可以包含泛型类型和值。

看起来没问题,但是当我想要合成时,我遇到了将合成中的 ISomething 转换为 Something 并获取其值的问题。 我不能将列表中的 ISomethings 转换为它们的类型,例如,转换为某些东西。

这里有什么技巧可以做,还是不能做?? (应该怎么做??) 谢谢 肯尼斯

public interface ISomething
{
  string Name { get; set; }
}

public class Something<T> : ISomething
{
   public string Name { get; set; }
   public T Value { get; set; }

   public Something(string name, T value)
   {
      Name = name;
      Value = value;
   }
}

public class CompositeSomething : Something<IList<ISomething>>
{
   public CompositeSomething (string name)
      : base(name, new List<ISomething>())
   {
   }

   public void Add(ISomething newSomething)
   {
      Value.Add(newComponent);
   }

   public void Remove(ISomething oldSomething)
   {
      Value.Remove(oldSomething);
   }
}


XmlNode BuildXml(Something something, XmlDocument document)
{
    XmlNode node = document.CreateNode(XmlNodeType.Element,
                   something.Name, "");
    foreach (ISomething item in compositeSomething.Value)
    {
        var isComposite = item is CompositeSomething;
        if (isComposite)
        {
            node.AppendChild(BuildXml((CompositeSomething)item, document));
        }
        else
        {
           var child = (Something<T>)item; // FAILS!!!!
           node.AppendChild(BuildXml(child,document));
        }
    }
    return node;
 }

【问题讨论】:

  • 听起来您根本不想要ISomething 的列表 - 为什么不将CompositeSomething 也设为通用,并带有Something&lt;T&gt; 的列表?
  • var child = (Something&lt;T&gt;)item; 无论您的其他代码如何,都不会编译,因为您没有将它放在通用方法中; BuildXml 不知道 T 是什么。如果你清楚地描述你想要达到的目标,你会得到更好的答案。
  • 好点,我添加了 并编译,但调用它失败

标签: c# list generics casting


【解决方案1】:

给你的非泛型接口也赋予一个对象类型的值属性并显式地实现它:

public interface ISomething
{
  string Name { get; set; }
  object Value { get; }
}

public class Something<T> : ISomething
{
   public string Name { get; set; }
   public T Value { get; set; }

   public Something(string name, T value)
   {
      Name = name;
      Value = value;
   }

   public object ISomething.Value
   {
      get { return Value; }
   }
}

更新

没有将非泛型对象转换为泛型类型对象并直接访问其值的内置机制。在这种情况下,您可以使用字典作为类型解析器(未经证实,直接在此处编写,无需编译器检查):

private Dictionary<Type, Action<ISomething>> _TypeResolver = new Dictionary<Type, Action<ISomething>>();

private void InitializeTypeResolver()
{
    _TypeResolver.Add(typeof(Something<int>), ForInteger);
    _TypeResolver.Add(typeof(Something<double>), ForDouble);
}

private void ForInteger(ISomething source)
{
    // We know, we will only be called for this specific type, so a hard cast is the way to go.
    var something = (Something<int>)source;

    var value = something.Value; // Will be of type int.
    // ToDo: Whatever you want with that integer.
}

private void ForDouble(ISomething source)
{
     var something = (Something<double>)source;
     var value = something.Value; // Will be of type double.
}

private void Resolve(ISomething something)
{
     // ToDo: nullity checks.
     Action<ISomething> action;

     if(_TypeResolver.TryGetValue(something.GetType(), out action))
     {
         action(something);
     }
     else
     {
         // ToDo: What shall we do with the drunken sailor (or unknown type)?             
     }
}

【讨论】:

  • 但这就像用对象制作列表一样......并且在我检索列表中的项目时丢失类型信息
【解决方案2】:

听起来问题只是简单地给出了ISomething 如何访问该值。

试试这个:

Something<int> something1 = new Something<int>("Foo", 42);

ISomething something = (ISomething)something1;

Something<int> something2 = something as Something<int>;
if (something2 != null)
{
    Console.WriteLine(something2.Value);
}

这给了我42

as Something&lt;int&gt; 仅在原始实例为 Something&lt;int&gt; 时才会产生值,否则将返回 null

另一种方法是像这样扩展你的接口和实现:

public interface ISomething
{
    string Name { get; set; }
    object Value { get; }  
}

public class Something<T> : ISomething
{
    public string Name { get; set; }
    public T Value { get; set; }
    object ISomething.Value { get { return Value; } }

    public Something(string name, T value)
    {
        Name = name;
        Value = value;
    }
}

然后你可以写:

Something<int> something1 = new Something<int>("Foo", 42);

ISomething something = (ISomething)something1;

Console.WriteLine(something.Value);

这又是42

您可以进一步扩展代码,为您提供如下值的类型:

public interface ISomething
{
    string Name { get; set; }
    object Value { get; }
    Type ValueType { get; }
}

public class Something<T> : ISomething
{
    public string Name { get; set; }
    public T Value { get; set; }
    object ISomething.Value { get { return Value; } }
    Type ISomething.ValueType { get { return typeof(T); } }

    public Something(string name, T value)
    {
        Name = name;
        Value = value;
    }
}

无论如何,您仍然需要使用反射来获取值,但界面无需反射即可将其提供给您。


下面是如何实现BuildXml

从这样的类开始:

public interface ISomething
{
    string Name { get; set; }
    object Value { get; }
    Type ValueType { get; }
}

public class Something<T> : ISomething
{
    public string Name { get; set; }
    public T Value { get; set; }
    object ISomething.Value { get { return Value; } }
    Type ISomething.ValueType { get { return typeof(T); } }

    public Something(string name, T value)
    {
        Name = name;
        Value = value;
    }
}

public class CompositeSomething : Something<IList<ISomething>>
{
    public CompositeSomething (string name)
        : base(name, new List<ISomething>())
    {
    }

    public void Add(ISomething newSomething)
    {
        Value.Add(newSomething);
    }

    public void Remove(ISomething oldSomething)
    {
        Value.Remove(oldSomething);
    }
}

你可以这样写BuildXml

XElement BuildXml<T>(Something<T> something)
{
    var compositeSomething = something as CompositeSomething;
    if (compositeSomething != null)
    {
        MethodInfo z =
            this
                .GetType()
                .GetMethod("BuildXml", BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
        return
            new XElement(
                compositeSomething.Name,
                compositeSomething.Value
                    .Select(x => z.MakeGenericMethod(x.ValueType).Invoke(this, new object[] { x }) as XElement));
    }
    else
    {
        return new XElement(something.Name, something.Value);
    }
}

我选择使用 LinqToXml 来构建 xml。

所以,给定这个输入:

var cs1 = new CompositeSomething("Foo");
cs1.Add(new Something<int>("Bar", 42));
cs1.Add(new Something<string>("Qaz", "Wix"));

var cs2 = new CompositeSomething("Rew");
cs2.Add(new Something<decimal>("Moo", 1.98m));
cs2.Add(cs1);

我可以运行BuildXml(cs2) 来获得这个输出:

<Rew>
  <Moo>1.98</Moo>
  <Foo>
    <Bar>42</Bar>
    <Qaz>Wix</Qaz>
  </Foo>
</Rew>

我想这就是你想要的。

【讨论】:

  • 是的,但我想用列表中的项目来做。而且我不知道项目类型..
  • @kfn - 我添加了一种简单的方法来获取值的类型,但我怀疑你想要更多的东西(你无法得到)。
  • 你的怀疑是对的,我想做更多..我想使用 typeinfo 来获取要转换的内容的信息..,我不想在调用构建函数时使用它..
  • @kfn - 你不能使用运行时类型信息来让你投射到任何东西。它只是行不通。您必须改为使用反射来调用方法。
  • 谢谢 - 可以使用 C++ 模板中的方法..;-)
【解决方案3】:

实现一个非泛型接口;

public interface IGetGenericTypeInstance
{
    object GenericTypeInstance();
}

现在我可以将对象转换为 IGetGenericTypeInstance 并且 GenericTypeInstance 会将属性作为类型对象返回。

【讨论】:

  • 所以我应该将 IGetGenerict... 接口添加到 Something 类,并在从列表中获取时通过对象? (并且只列出对象??)
  • 是的,将它添加到Something类中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-27
  • 1970-01-01
  • 2020-06-16
  • 1970-01-01
相关资源
最近更新 更多