【问题标题】:Comparison of unspecified generic properties未指定的通用属性的比较
【发布时间】:2017-07-25 10:05:03
【问题描述】:

考虑以下代码:

public interface IIdentifiable<T>
{
    T Id { get; set; }
}

public interface IViewModel
{
}

public class MyViewModel1 : IViewModel, IIdentifiable<int>
{
    public string MyProperty { get; set; }

    public int Id { get; set; }
}

public class MyViewModel2 : IViewModel, IIdentifiable<string>
{
    public string MyProperty { get; set; }

    public string Id { get; set; }
}

我也有使用 ViewModels 的类:

public class Loader<T> where T: IViewModel
{
    public void LoadData()
    {
        /*some important stuff here*/

        if (typeof(IIdentifiable<??>).IsAssignableFrom(typeof(T)))
        {                     // ^- here's the first problem
            data = data.Where(d => _dataSource.All(ds => ((IIdentifiable<??>) ds).Id != ((IIdentifiable<??>) d).Id)).ToList();
        }                                                             // ^---- and there the second ----^

        /*some important stuff here too*/
    }
}

现在,如您所见,我拥有的视图模型可能会实现 IIdentifiable&lt;&gt; 接口。我想检查一下,如果是真的, 我想确保我的data 列表不包含我的_dataSourse 列表中已经存在的任何条目。

所以我有两个问题:

  1. 我不知道IIdentifiable&lt;&gt; 的通用括号里有什么,可能是intstring 甚至GUID。 我试过typeof(IIdentifiable&lt;&gt;).IsAssignableFrom(typeof(T)),这是正确的语法,但它总是返回false。 有没有办法在不知道确切泛型类型的情况下检查T 是否为IIdentifiable&lt;&gt;

  2. 如果第一个问题有答案,我也想知道如何在不知道它们类型的情况下比较 Id 字段。 我发现this answer 非常有用,但它并没有涵盖我的 具体情况。

我知道如果我将Loader&lt;T&gt; 类设为两种类型Loader&lt;T,K&gt; 的泛型,我可能可以解决这个问题,其中K 将是 输入IIdentifiable&lt;&gt;,但我想知道是否有其他解决方案。


附:除了我的第一个问题:我也很好奇,如果没有指定IIdentifiable&lt;&gt; 的泛型类型时返回false,为什么可以写类似typeof(IIdentifiable&lt;&gt;).IsAssignableFrom(typeof(T)) 的东西?


编辑:我想,事后看来,我明白为什么我不能直截了当地写代码——因为可能有集合ICollection&lt;IViewModel&gt;,其中的条目实现了不同类型的IIdentifiable&lt;&gt; (或者根本不实现它),这样的检查会尴尬地失败。然而,也许有一种方法可以在有一些限制的情况下执行类似的操作,但无需为我的Loader 创建第二个通用参数?

【问题讨论】:

  • 我不确定你想要达到什么目标,但如果你的 IIdentifiable 实现 IComparable 可能会有所帮助

标签: c# generics


【解决方案1】:

尝试在您的Loader&lt;T&gt; 中添加两种方法:

public bool CanCast<TId>()
{
    var identifiableT = typeof(IIdentifiable<>).MakeGenericType(typeof(TId));
    return identifiableT.IsAssignableFrom(typeof(T));
}

public IEnumerable<IIdentifiable<TId>> Filter<TId>(IEnumerable<T> data)
{
    return data.Where(d => _dataSource.All(
      ds => !((IIdentifiable<TId>) ds).Id.Equals(((IIdentifiable<TId>) d).Id)));
}

然后在LoadData

if (CanCast<int>())
    data = Filter<int>(data);
else if (CanCast<Guid>())
    data = Filter<Guid>(data);
// and so om

【讨论】:

  • 感谢您的回答,Aleks,竖起大拇指!虽然我不能说它满足我的需要,因为我不想引入第二个泛型参数,更不想枚举所有可能的情况。
【解决方案2】:

好吧,我建议您始终使用字符串进行标识。您可以将 int 和 guid 转换为字符串。如果你想确保使用正确的类型,那么你可以在字符串前面加上类型信息。

但是,我确实认为你的算法的性能会很差,因为你会循环 2 个容器,所以它会是 O(n * m)。

因此,如果两个来源都来自数据库,则最好执行适当的 SQL 查询,或者如果您在代码中执行此操作,则最好使用字典。或者,如果数据被正确排序,您可以更有效地找到重复项。

顺便说一句,泛型在 C# 中非常有限。有时使用 ˋFuncˋ 可能会有所帮助,但即便如此,您也必须为算法提供额外信息。

【讨论】:

    【解决方案3】:

    我们应该分两步解决您的问题(因为这里确实有两个问题需要解决)。

    首先,对您的界面进行以下更改IIdentifiable&lt;T&gt;

    public interface IIdentifiable<T>
        where T : IEquatable<T>
    {
        T Id { get; set; }
    }
    

    这将确保您可以正确比较 Id 属性。

    其次,在您的LoadData() 方法中,将if 语句更改为

        if (T is IIdentifiable<T>)
        {                     // ^- here's the first problem
            data = data.Where(d => _dataSource.All(ds => ((IIdentifiable<T) ds).Id != ((IIdentifiable<T) d).Id)).ToList();
        }
    

    【讨论】:

      猜你喜欢
      • 2013-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多