【问题标题】:Is there a workaround to use static methods by a generic class?是否有一种解决方法可以通过泛型类使用静态方法?
【发布时间】:2013-11-23 07:36:54
【问题描述】:

我有一个相当简单的问题,但在 C# 中似乎没有解决方案。

我有大约 100 个 Foo 类,每个类都实现了一个 static FromBytes() 方法。还有一些泛型类应将这些方法用于自己的FromBytes()。但是泛型类不能使用static FromBytes() 方法,因为T.FromBytes(...) 是非法的。

是我遗漏了什么还是没有办法实现这个功能?

public class Foo1
{
    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // build Foo1 instance
        return new Foo1()
        {
            Property1 = bytes[index++],
            Property2 = bytes[index++],
            // [...]
            Property10 = bytes[index++]
        };
    }

    public int Property1 { get; set; }
    public int Property2 { get; set; }
    // [...]
    public int Property10 { get; set; }
}

//public class Foo2 { ... }
// [...]
//public class Foo100 { ... }

// Generic class which needs the static method of T to work
public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(T.FromBytes(bytes, ref index)); // T.FromBytes(...) is illegal
        }

        return listOfFoo;
    }
}

我认为选择一个答案作为公认答案是不公平的,毕竟所有答案和 cmets 都以不同的方式做出了贡献,他们的观点不同。如果有人对不同方法的优缺点进行了很好的概述,那就太好了。在它对未来的开发者有最大的帮助之后应该被接受。

【问题讨论】:

  • 尝试扩展方法。
  • 嗨@Muctadir,不幸的是,我在泛型扩展方法中遇到了与泛型类中相同的问题。
  • 如何调用静态ListOfFoo&lt;T&gt;.FromBytes(...) 方法?无论如何,您都必须指定类型参数,不是吗?例如:ListOfFoo&lt;Foo1&gt;.FromBytes(...)。然后您可以将委托作为附加参数传递(如Servy proposed)。还是通过反射调用它?

标签: c# generics reflection


【解决方案1】:

您能在实用程序类中实现静态FromBytes 方法吗?然后做这样的事情?

listOf.Add(Utility.FromBytes<T>(bytes, ref index));

如果每个方法FromBytes 有很大的不同,那么该实用方法可能会有点难看,但也不会太糟糕,反正所有代码都存在于每个类中。

类似这样的:

public static class Utility
{
    public static T FromBytes<T>(byte[] bytes, ref int index)
    {
          if (typeof(T) is Foo1)
          {
               return Foo1.GetBytes(bytes, ref index);
          }
          //etc....
    }
}

您也可以在此处隐藏该反射代码,正如其他答案所指出的那样。不幸的是,似乎没有一种非常干净的方法可以做到这一点,但将杂乱的东西隐藏在实用方法中可能是最好的选择。

【讨论】:

  • 感谢您的关注,但接口不能有静态方法。抽象类也不能有任何静态的东西。
  • 我认为你需要依靠反射。
  • 有趣的是我其实不知道。我想反射是这样做的方法。
  • 这只是将问题改为:“如何实现Utility.FromBytes
  • @KarstenGutjahr Cast 是可能的。需要一点工作。值类型昂贵。这就是你的方法。[return (T)(object)someType]
【解决方案2】:

问题是您尝试使用FromBytes 作为扩展方法,但实际上并非如此。根据我收集到的信息,您正在尝试为任何 &lt;T&gt; 调用适当的 FromBytes,正如您已经为 T 创建的任何类中所定义的那样。

你需要做的是通过反射调用方法。

请尝试查看其中一些主题以获取有关如何完成此操作的帮助。

Use Reflection to call generic method on object instance with signature: SomeObject.SomeGenericInstanceMethod<T>(T argument)

How do I use reflection to call a generic method?

Calling generic method using reflection in .NET

How to call generic method with a given Type object?

【讨论】:

  • 如果我使用反射来找出我可以调用“Foo42.FromBytes()”的类型。但我无法将结果实例添加到列表中:'listOf.Add(Foo42.FromBytes(Bytes, ref index));' 给我“参数类型 'Foo42' 不可分配给参数类型 T。”。
  • 嗯,你试过投射结果吗? (T)Foo42.FromBytes(Bytes, ref index)?
  • 嗯,抱歉,我无法提供更多帮助。但我不确定。也许尝试重新发布这个问题,但在标题中进行反思。您可能会吸引反思大师。
  • 尝试转换为object,然后转换为T(T)((object)Foo42.FromBytes(Bytes, ref index))
【解决方案3】:

你可以试试这样的:

sealed class RetType
{
    public object Value
    {
        get;
        private set;
    }

    public int Index
    {
        get;
        private set;
    }

    public RetType(object value, int index)
    {
        Value = value;
        Index = index;
    }
}

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    static readonly Dictionary<Type, Func<byte[], int, RetType>> dic = new Dictionary<Type, Func<byte[], int, RetType>>
    {
        {
            typeof(Foo1),
            new Func<byte[], int, RetType>((bytes, index) =>
            {
                var value = Foo1.FromBytes(bytes, ref index);

                return new RetType(value, index);
            })
        }
        // add here others Foo
    };

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOf = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            var o = dic[typeof(T)](bytes, index);

            listOf.Add((T)o.Value);

            index = o.Index;
        }

        return listOf;
    }
}

您构建查找以查找要调用的方法来构建实例。

【讨论】:

  • 所以您是说字典必须为每个Foo 手动填写?这听起来不是一个很好的解决方案。
  • 显然,我同意这不是一个好的解决方案,但它是一个解决方案。另一个明显的选择是上面提出的反射。
  • 我也对不太好的解决方案感兴趣,但对于编译器来说,'RetType' 的 'T' 与 'ListOfFoo' 中的 'T' 不同, 只是共享同一个名字。因此编译器不能将一个转换为另一个... :-(
  • @KarstenGutjahr 好的,我重新编写了代码并对其进行了测试,看看吧。
  • 很好,这似乎是合理的。我只是担心这段代码会被压缩为(T)((object)Foo42.FromBytes(Bytes, ref index)),在您发表评论后 20 分钟在这里发表评论。
【解决方案4】:

您可以使用反射来查找该类型的方法,然后为它构建一个委托。比如:

delegate T FromBytesFunc<T>(byte[] bytes, ref int index);

public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
{
    FromBytesFunc<T> fromBytes =
        (FromBytesFunc<T>)Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>), typeof(T).GetMethod("FromBytes")

    var count = bytes[index++];
    var listOf = new ListOfFoo<T>();
    for (var i = 0; i < count; i++)
    {
        listOf.Add(fromBytes(bytes, ref index));
    }

    return listOf;
}

【讨论】:

  • 这看起来很有趣,但是“MethodInfo”没有方法“CreateDelegate()”。 ;-)
  • @KarstenGutjahr 确实如此,但仅在 .Net 4.5 中,我没有注意到。
【解决方案5】:

最好的选择是简单地接受特定的 FromBytes 函数作为通用 FromBytes 函数的委托。这避免了使用反射带来的性能成本和缺乏编译时可验证性。

public delegate T FromBytesFunc<T>(byte[] bytes, ref int index);
public static List<T> FromBytes<T>(byte[] bytes, ref int index,
    FromBytesFunc<T> function)
{
    var count = bytes[index++];
    var listOfFoo = new List<T>();
    for (var i = 0; i < count; i++)
    {
        listOfFoo.Add(function(bytes, ref index));
    }

    return listOfFoo;
}

请注意,如果您将方法(而不是它所在的类)设为泛型,您可以让编译器推断出泛型参数。可以这样调用:

var list = SomeClass.FromBytes(bytes, ref index, Foo1.FromBytes);

【讨论】:

    【解决方案6】:

    不确定这是否适合您,但这是一种选择:

    1. 定义一个接口IHaveFromBytesMethod&lt;T&gt;,它定义了一个FromBytes(...)实例方法。
    2. 让所有Foo# 类型实现这个接口。 (如果您不希望此实例方法立即可见,则可以显式实现该接口。)
    3. 实例方法的所有实现都只是将调用转发到该类型的静态方法。
    4. ListOfFoo&lt;T&gt; 类中限制类型参数T 以实现此接口并提供无参数构造函数。
    5. 当您需要静态方法时,使用无参数构造函数构造一个 T 类型的新虚拟对象,然后在该虚拟实例上调用实例方法。

    界面:

    public interface IHaveFromBytesMethod<T>
    {
        T FromBytes(byte[] bytes, ref int index);
    }
    

    Foo# 类之一:

    public class Foo1 : IHaveFromBytesMethod<Foo1>
    {
        public Foo1()
        {
            // ...
        }
    
        public static Foo1 FromBytes(byte[] bytes, ref int index)
        {
            // ...
        }
    
        public Foo1 FromBytes(byte[] bytes, ref int index)
        {
            // within the instance method simply call the static method
            return Foo1.FromBytes(bytes, ref index);
        }
    }
    

    修改后的ListOfFoo&lt;T&gt;类:

    // requires T to implement the interface and provide a parameterless ctor!
    public class ListOfFoo<T> : System.Collections.Generic.List<T>
        where T : IHaveFromBytesMethod<T>, new()
    {
        public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
        {
            // create dummy instance for accessing static method via instance method
            T dummy = new T();
            var count = bytes[index++];
            var listOfFoo = new ListOfFoo<T>();
            for (var i = 0; i < count; i++)
            {
                // instead of calling the static method,
                // call the instance method on the dummy instance
                listOfFoo.Add(dummy.FromBytes(bytes, ref index));
            }
    
            return listOfFoo;
        }
    }
    

    【讨论】:

    • 这真的很难看。从逻辑上讲,构造Foo 的实例来构造它的另一个实例是没有意义的。
    • 这是真的。但是,这个页面上的所有解决方案在某种程度上不都是丑陋的吗?我只是想提出一个无需反思即可工作的替代方案。
    【解决方案7】:

    我要感谢你们所有人,你们给了我一些思考的见解。我已经考虑了每种方法并考虑了它的优缺点并编写了以下代码。你有什么想法?我认为这是对可用性、可读性和性能的一个很好的折衷。它只是缺少编译器检查。

    public class ListOfFoo<T> : System.Collections.Generic.List<T>
    {
        private static readonly FromBytesFunc<T> BytesFromFunc = 
            (FromBytesFunc<T>)System.Delegate.CreateDelegate(
                typeof(FromBytesFunc<T>),
                typeof(T).GetMethod("FromBytes"));
    
        private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index);
    
        public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
        {
            var count = bytes[index++];
            var listOfFoo = new ListOfFoo<T>();
            for (var i = 0; i < count; i++)
            {
                listOfFoo.Add(BytesFromFunc(bytes, ref index));
            }
    
            return listOfFoo;
        }
    }
    

    【讨论】:

      【解决方案8】:

      这将是所有未来观众的摘要。

      由于您不能只调用方法,因此主要问题是获取委托。有两种方法:

      • 通过反射
      • 来自调用者

      您可能认为反射很慢,但性能不是问题。如果委托存储在静态字段中,则每个类只需执行一次,因为泛型类型不共享静态成员。

      编译时可验证性是一个问题。如果您非常关心编译时可验证性,则应该从调用者传递委托。如果您更关心干净的方法调用,则必须牺牲编译时可验证性。

      PS:有些人建议使用字典或 switch/case 或 if/else 来存储代表。这是你不应该做的事情。这与将委托存储在泛型类的静态字段中没有优势(泛型类型不共享静态成员)。

      【讨论】:

        猜你喜欢
        • 2016-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-16
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多