【问题标题】:Casting down from generic type within function type parameters (error)在函数类型参数中从泛型类型向下转换(错误)
【发布时间】:2015-04-22 23:48:48
【问题描述】:

我遇到的问题是,在下面的示例中,我可以将MyType<T> 转换为IMyType,很好。但是我不能将Func<MyType<T>, bool> 转换为Func<IMyType, bool>

这没有任何意义,我想知道是否有办法通过不同的架构来解决它?

我不想使用反射。

我也想了解它失败的原因。

.NET 4.5.2 中的代码失败。这里还有在线版本 - http://goo.gl/13z4xg - 以同样的方式失败。

using System;
using System.Collections.Generic;

public interface IMyType
{
    string Text { get; }
    int Number { get; }
}

public class MyType<T> : IMyType where T : SomeOtherType
{
    public MyType()
    {
        Text = "Hello";
        Number = 99;
    } 
    public string Text { get; private set; }
    public int Number { get; private set; }
}

public abstract class SomeOtherType
{
    public int Id { get; set; }
    public string Title { get; set; }
}

public class ConcreteType : SomeOtherType
{
    public string Description { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var ok = (IMyType) new MyType<ConcreteType>();
        Console.Write("I can cast MyType<T> to IMyType here");
        var list = new List<Func<IMyType, bool>>();
        Func<MyType<ConcreteType>, bool> func = type => false;
        // list.Add(func); // Compiler error
        list.Add((Func<IMyType,bool>)func); // Runtime cast error
        Console.Write("Got here so ok"); // (It doesn't get here...)
    }
}

【问题讨论】:

  • 您使用的是什么版本的运行时?这并没有给我一个错误(除了由缺少 func 名称引起的编译错误)。请注意,这是假设 IMyTypeMyTypeSomeOtherTypeConcreteType : SomeOtherType 的定义为空
  • 您发布的代码示例“很好”(因为它似乎没有任何方法可以引发您所描述的运行时异常)。你的意思是写new List&lt;Func&lt;IMyType, bool&gt;&gt;() 吗?这将无法以一种易于理解的方式工作(尽管在编译时,而不是运行时)。显然,您还没有发布失败的实际代码。请编辑您的帖子,使其包含可靠地重现问题的a good, minimal, complete code example
  • @DaxFohl 是的,语法错误
  • @PeterDuniho 显然,我没有发布实际代码,因为如果我这样做了,我显然会丢掉工作。我现在将用一个例子进行编辑。它在运行时确实会失败。
  • 重点是,无论您发布什么代码,必须重现您所询问的问题。第一个代码示例没有。通过您的新示例,我发现正如我所猜测的那样,您犯了一个众所周知的错误。 Stack Overflow 上有很多帖子讨论了这个问题;我建议将其作为副本关闭,但由于缺乏好的代码示例,我已经提议关闭它。随意从以下帖子列表中投票关闭作为您最喜欢的副本:stackoverflow.com/q/2033912/3538012stackoverflow.com/q/8567206/3538012,(继续...)

标签: c# generics casting


【解决方案1】:

对不起...我认为各种答案很好地解释了为什么你不想这样做。即使你认为你这样做(一个足够常见的错误,所以没有理由为此感到难过)。我没有想到,在阅读了所有这些问题及其答案之后,您不会觉得自己的问题没有足够的答案。我不确定我可以为它们添加多少,但我会尝试……


简短的版本:如果允许您将Func&lt;MyType&lt;ConcreteType&gt;, bool&gt; 的实例添加到Func&lt;IMyType, bool&gt; 的列表中,那么其他一些代码可以将该实例作为Func&lt;IMyType, bool&gt; 拉出,然后尝试调用它,传递给委托一个IMyType 的实例,而不是 MyType&lt;ConcreteType&gt;

但是,您的方法(假设是委托声明)只需要 MyType&lt;ConcreteType&gt;! 的实例!如果通过IMyType 的其他一些实现,它会做什么?


当编译器在list.Add(func) 处为您提供编译时错误时,编译器试图 帮助您避免犯该错误。您使用强制转换向编译器隐藏了错误,但这并没有改变编译器试图拯救您的基本危险,因此当您尝试强制转换并阻止您时,运行时必须介入。

在编译时阻塞代码会更好,但是当您使用强制转换来声称您知道编译器不知道的类型时,这是不可能的。但是最好在演员表中得到运行时错误,而不是稍后当您尝试将IMyType 的其他一些实现传递给委托所代表的方法时。


现在,您给出的代码示例并没有很清楚地说明为什么您认为这样做甚至是有利的。因此,不可能知道哪种替代方案最适合您的情况。

如果委托引用的方法确实需要知道类型是MyType&lt;ConcreteType&gt;,那么你就卡住了。将引用该方法的委托放在任何 IMyType 可以传递给它的上下文中根本不安全。你不知何故把自己画到了一个角落,你需要重新考虑你的设计,因为它目前有一个根本性的缺陷。

但是请注意,如果您真的在您的方法中只使用对象的IMyType-ness,那么解决方案就是以这种方式声明它。例如,如果将您显示的 lambda 表达式分配给 Func&lt;IMyType, bool&gt; 类型的变量,您可以将其添加到列表中,则编译得很好。

或者,如果您100% 确定除了MyType&lt;ConcreteType&gt; 之外,没有代码会尝试使用IMyType 的实现来调用委托,您可以将错误的委托类型包装在正确委托类型的实例。例如:

list.Add(o => func((MyType<ConcreteType>)o));

当然,通过这样做,您将完全删除使用通用 List&lt;T&gt; 对象的大部分类型安全功能。它肯定有那种“糟糕的代码味道”。但它会编译,甚至会运行,只要您从不违反规则并尝试使用 IMyType 的不同实现来调用委托。

【讨论】:

  • 谢谢。我了解类型安全方面,我真的在寻找一种针对我描述的特定情况进行编程的方法,以及对“为什么”的解释(您已在此处提供)。 FWIW我不同意“你不想这样做”。在这种情况下,我需要这样做,并且我正在处理潜在的类型安全问题(在我的真实代码中)。
【解决方案2】:

回答我自己的问题,或者至少回答“我怎样才能让它工作”部分。

问题在于差异(参见 cmets 中的重复项)。在这种情况下,“我怎样才能让它工作”部分的答案是使用List&lt;T&gt;.ConvertAll(正如这里所建议的 - Jon Skeet 的C# variance problem: Assigning List<Derived> as List<Base>)。

对于我的需要,这个解决方案是完美的。 在我的情况下,创建新列表的“费用”并不重要。

所以我所做的是扩展示例,并使用List&lt;T&gt;.ConvertAll 以便非泛型感知实例可以访问这些函数。链接到这里的代码 - http://goo.gl/MVnYhX - 代码也在下面:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public interface IMyType
    {
        string Text { get; }
        int Number { get; }
    }
    public class MyType<T> : IMyType where T : SomeOtherType
    {
        public MyType()
        {
            Text = "Hello World";
            Number = 999;
        } 
        public string Text { get; private set; }
        public int Number { get; private set; }
    }
    public abstract class SomeOtherType
    {
        public int Id { get; set; }
        public string Title { get; set; }
    }
    public class ConcreteType : SomeOtherType
    {
        public string Description { get; set; }
    }
    public interface IFaceThatAccessesIt
    {
        IList<Func<IMyType, bool>> GetList();
    }
    public class ClassThatAccessesIt<T> : IFaceThatAccessesIt where T : SomeOtherType
    {
        /// <summary>
        /// Generically typed inner list
        /// </summary>
        private readonly List<Func<MyType<T>, bool>> _innerList = new List<Func<MyType<T>, bool>>();

        /// <summary>
        /// Get list converted
        /// </summary>
        /// <returns></returns>
        public IList<Func<IMyType, bool>> GetList()
        {
            var output = _innerList.ConvertAll(func =>
            {
                Func<IMyType, bool> outFunc = type => func.Invoke((MyType<T>)type);
                return outFunc;
            });
            return output;
        }

        /// <summary>
        /// Add to list
        /// </summary>
        /// <param name="func"></param>
        public void AddToList(Func<MyType<T>, bool> func)
        {
            _innerList.Add(func);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // get class / instance the uses said list
            var asClass = new ClassThatAccessesIt<ConcreteType>();
            var asInterface = (IFaceThatAccessesIt) asClass;

            // add into list (via class, not interface)
            asClass.AddToList(input => input.Number == 999);
            asClass.AddToList(input => input.Text == "Hello World");

            // get list from interface
            var listFromInterface = asInterface.GetList();

            // get function 1
            var func1 = listFromInterface.First();

            // invoke
            var inputInstance = new MyType<ConcreteType>();
            var result = func1.Invoke(inputInstance);

            // print
            Console.WriteLine("This result should be 'True'... {0}",result);

        }
    }
}

【讨论】:

  • 这仍然不是类型安全的。当你调用GetList()[0] 时,它会返回一个Func&lt;IMyType, bool&gt;,但该函数实际上期望传入一个MyType&lt;T&gt;,如果你传入其他一些IMyType,那么它会在第42 行崩溃。
【解决方案3】:

如果不知道您正在做的事情的更大范围,就不可能说出您需要做什么,但我猜是对于您创建的 IMyType 的每个对象,您想要添加一些函数来调用就可以了。

如果是这种情况,一种类型安全的方法是将它们连接到您的 AddToList,这样您就可以保留 List&lt;Func&lt;bool&gt;&gt; 作为您的成员,将 MyType 完全从签名中移除:

public interface IFaceThatAccessesIt
{
    IList<Func<bool>> GetList();
}

public class ClassThatAccessesIt<T> : IFaceThatAccessesIt where T : SomeOtherType
{
    private readonly List<Func<bool>> _innerList = new List<Func<bool>>();

    public IList<Func<bool>> GetList()
    {
        return _innerList;
    }

    public void AddToList(MyType<T> thing, Func<MyType<T>, bool> func)
    {
        _innerList.Add(() => func(thing));
    }
}

【讨论】:

  • 注意,如果你这样做,如果你在ClassThatAccessesIt 中没有其他任何东西,那么整个层次结构就会变成不必要的开销;您可以从您的主代码中创建List&lt;Func&lt;bool&gt;&gt;,然后将其传递到您需要的任何地方,无需大张旗鼓地调用list.Add(() =&gt; func(thing)) inline。这种方式更容易理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多