簡體   English   中英

動態設置泛型類型參數

[英]Dynamically set generic type argument

從我的問題繼在這里 ,我試圖創建一個通用的價值相等比較。 我之前從未玩過反射,所以不確定我是否在正確的軌道上,但無論如何我到目前為止都有這個想法:

bool ContainSameValues<T>(T t1, T t2)
{
    if (t1 is ValueType || t1 is string)
    {
        return t1.Equals(t2);
    }

    else 
    {
        IEnumerable<PropertyInfo> properties = t1.GetType().GetProperties().Where(p => p.CanRead);
        foreach (var property in properties)
        {
            var p1 = property.GetValue(t1, null);
            var p2 = property.GetValue(t2, null);

            if( !ContainSameValues<p1.GetType()>(p1, p2) )
                return false;
        }
    }
    return true;
}

這不編譯,因為我無法弄清楚如何在遞歸調用中設置T的類型。 是否可以動態執行此操作?

這里有幾個相關的問題,我已經閱讀了但是我無法完全了解它們如何適用於我的情況。

如果您樂意根據靜態知道的屬性類型進行比較,則可以避免反映調用。

這依賴於3.5中的表達式以簡單的方式進行一次性反射,可以更好地做到這一點,以減少極端嵌套類型的工作量,但這應該適合大多數需求。

如果必須解決運行時類型,則需要一定程度的反射(盡管如果再次緩存每個屬性訪問和比較方法,這會很便宜),但這本身就要復雜得多,因為子屬性上的運行時類型可能不匹配所以,為了完全普遍,你必須考慮以下規則:

  • 認為不匹配的類型不相等
    • 簡單易懂,易於實施
    • 不太可能是一個有用的操作
  • 在類型分歧的點上使用標准的EqualityComparer<T>.Default實現兩者並且不再進一步遞歸
    • 實施起來更簡單,更難實施。
  • 如果它們具有本身相同的共同屬性子集,則認為是相等的
    • 復雜,不是非常有意義
  • 如果它們共享相同的屬性子集(基於名稱和類型),則認為它們是相等的
    • 復雜,進入鴨子打字

還有其他各種選擇,但這應該是思考為什么完整的運行時分析很困難。

(請注意,我已經改變了你''葉子'終止守衛是我認為優越的,如果你想因某種原因只使用刺痛/價值類型而感到自由)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;


class StaticPropertyTypeRecursiveEquality<T>
{
    private static readonly Func<T,T, bool> actualEquals;

    static StaticPropertyTypeRecursiveEquality()
    {
        if (typeof(IEquatable<T>).IsAssignableFrom(typeof(T)) || 
            typeof(T).IsValueType ||
            typeof(T).Equals(typeof(object)))
        {
            actualEquals = 
                (t1,t2) => EqualityComparer<T>.Default.Equals(t1, t2);
        }
        else 
        {
            List<Func<T,T,bool>> recursionList = new List<Func<T,T,bool>>();
            var getterGeneric = 
                typeof(StaticPropertyTypeRecursiveEquality<T>)
                    .GetMethod("MakePropertyGetter", 
                        BindingFlags.NonPublic | BindingFlags.Static);
            IEnumerable<PropertyInfo> properties = typeof(T)
                .GetProperties()
                .Where(p => p.CanRead);
            foreach (var property in properties)                
            {
                var specific = getterGeneric
                    .MakeGenericMethod(property.PropertyType);
                var parameter = Expression.Parameter(typeof(T), "t");
                var getterExpression = Expression.Lambda(
                    Expression.MakeMemberAccess(parameter, property),
                    parameter);
                recursionList.Add((Func<T,T,bool>)specific.Invoke(
                    null, 
                    new object[] { getterExpression }));                    
            }
            actualEquals = (t1,t2) =>
                {
                    foreach (var p in recursionList)
                    {
                        if (t1 == null && t2 == null)
                            return true;
                        if (t1 == null || t2 == null)
                            return false;
                        if (!p(t1,t2))
                            return false;                            
                    }
                    return true;
                };
        }
    }

    private static Func<T,T,bool> MakePropertyGetter<TProperty>(
        Expression<Func<T,TProperty>> getValueExpression)
    {
        var getValue = getValueExpression.Compile();
        return (t1,t2) =>
            {
                return StaticPropertyTypeRecursiveEquality<TProperty>
                    .Equals(getValue(t1), getValue(t2));
            };
    }

    public static bool Equals(T t1, T t2)
    {
        return actualEquals(t1,t2);
    }
}

為了測試,我使用了以下內容:

public class Foo
{
    public int A { get; set; }
    public int B { get; set; }
}

public class Loop
{
    public int A { get; set; }
    public Loop B { get; set; }
}

public class Test
{
    static void Main(string[] args)
    {
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<String>.Equals(
            "foo", "bar"));
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<Foo>.Equals(
            new Foo() { A = 1, B = 2  },
            new Foo() { A = 1, B = 2 }));
        Console.WriteLine(StaticPropertyTypeRecursiveEquality<Loop>.Equals(
            new Loop() { A = 1, B = new Loop() { A = 3 } },
            new Loop() { A = 1, B = new Loop() { A = 3 } }));
        Console.ReadLine();
    }
}

你需要使用反射調用方法,如下所示:

MethodInfo genericMethod = typeof(SomeClass).GetMethod("ContainSameValues");
MethodInfo specificMethod = genericMethod.MakeGenericMethod(p1.GetType());
if (!(bool)specificMethod.Invoke(this, new object[] { p1, p2 }))

但是,您的方法首先不應該是通用的; 它應該只需要兩個object參數。 (或者,如果它是通用的,它應該以泛型類型緩存屬性和委托)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM