两种通用类型之间的转换失败

时间:2016-04-06 16:35:57

标签: c# linq generics types generic-programming

我正在尝试使用类型为T的linq表达式创建一个对象类型(MyObject)。我的类声明T的值必须是BaseModel类型(这是我创建的对象)。下面是MyObject的构造方式:

public class MyObject<T> where T : BaseModel
{
    public Type MyType;

    public Expression<Func<T, bool>> MyExpression;
}

我的所有模型都继承自BaseModel。例如:

public class MyModel : BaseModel
{
    public string Name { get; set; }
}

我的对象在通用静态类中使用:

public static class MyStaticClass
{
    private static Dictionary<MyObject<BaseModel>, string> MyDictionary = new Dictionary<MyObject<BaseModel>, string>();

    public static AddMyObjectsToDictionary(List<MyObject<BaseModel>> myObjects)
    {
        //CODE
    }

    //REST OF CODE
}

然后,当我的应用加载时,会执行以下(此处引发错误)

List<MyObject<BaseModel>> myObjects = new List<MyObject<BaseModel>>();

myObjects.Add(new MyObject<MyModel>()
{
    MyType = typeof(MyModel),
    MyExpression = p => p.Name == "myname"
});

MyStaticClass.AddMyObjectsToDictionary(myObjects);

使用命名空间抛出完全错误消息,以显示每个对象所在的项目:

  

无法从'ProjectANamespace.MyObject<ProjectBNamespace.MyModel>'转换为'ProjectANamespace.MyObject<ProjectANamespace.BaseModel>'

我需要能够在MyModel中创建一个通用表达式,但是我不能在MyStaticClass中指定MyModel,因为它是一个泛型类,它与BaseModel一起位于另一个项目中。

任何人都有任何想法如何解决这个问题?

2 个答案:

答案 0 :(得分:4)

MyObject<MyModel>不是MyObject<BaseModel>,就像List<string>不是List<object>一样。它在泛型之外的工作原理相同 - 一般概念称为 variance ,并描述了类型的有效替换。由于MyObject既不是T的共变体也不是反变体,MyObject<T>永远不会是MyObject<U>,因为任何不同的T和U都是

接口和委托中的泛型类型参数既可以是共变量也可以是反变体,但不能同时兼具。例如,您的Func<T, bool>是反式变体,因为您可以替换从T而非T本身派生的类型。因此,Func<BaseModel, bool>可以转换为Func<MyModel, bool>(正如MyModel可以作为参数“而不是BaseModel)传递,但反之亦然。类似地,Func<T, U>相对于TU的共变体是反变量的 - 您不能从具有返回类型的函数返回object string

因此,即使您将定义更改为使用界面添加方差,您也只能获得反差异,而不是您想要的协方差。太糟糕了 - 没有安全的方法可以做到这一点。

相反,如果您需要非通用接口,只需添加非通用接口即可。而已。而不是List<MyObject<BaseModel>>,您将拥有List<IMyObject>,您可以根据需要使用Expression(可能通过投射,或仅展示简单的Expression<Func<T, bool>>而不是OrderedDict)。

答案 1 :(得分:0)

要做到这一点,我相信你需要使用c#4+的Covariance功能。特别是,我认为您需要使用out修饰符。经过一些测试,我发现这不适用于课程。但是,如果在模式中引入接口,则可以使其工作。例如,以下编译

var root = { name: "root" };
var hosts = [
  { name: "host1", type: "host" },
  { name: "host2", type: "host" },
  { name: "host3", type: "host" }
];

var nodes = [root].concat(hosts);
var links = hosts.map(function(host) {
  return { source: root, target: host }
});

hosts.forEach(function(host) {
  var hostNum = host.name.substr(4);
  for(var i = 0; i <= 5; i++) {
    var vm = {
      name: "vm-" + hostNum + "-" + i,
      type: 'vm'
    }
    nodes.push(vm);
    links.push({
      source: host,
      target: vm
    })
  }
});

var force = d3.layout.force()
  .size([window.innerWidth, window.innerHeight])
  .nodes(nodes)
  .links(links)
  .charge(-1500)
  .gravity(0.1)
  .on('tick', update)
  .start();

var svg = d3.select('body')
  .append('svg')
  .attr({
    width:  window.innerWidth,
    height: window.innerHeight
  })

var circles = svg.selectAll('circle')
  .data(force.nodes())
circles.enter()
  .append('circle')
  .attr({
    r: function(d) { return d.type == 'vm' ? 14 : 20; },
    fill: '#1661FE'
  });

var lines = svg.selectAll('line')
  .data(force.links())
lines.enter()
  .append('line')
  .attr({
    fill: 'none',
    stroke: '#1661FE',
    'stroke-width': 3
  });

var texts = svg.selectAll('text')
  .data(force.nodes())
texts.enter()
  .append('text')
  .text(function(d) { return d.name; })
  .attr({
    fill: 'white',
    'text-anchor': 'middle',
    dy: '30'
  })
  .style({
    'font-family': "Verdana, Helvetica, Sans-Serif",
    'font-size': 12
  });

function update() {
  circles.attr({
    cx: function(d) { return d.x; },
    cy: function(d) { return d.y; }
  });

  texts.attr({
    x: function(d) { return d.x; },
    y: function(d) { return d.y; }
  })

  lines.attr({
    x1: function(d) { return d.source.x},
    y1: function(d) { return d.source.y},
    x2: function(d) { return d.target.x},
    y2: function(d) { return d.target.y},
  })
}