【问题标题】:C# generic with constant带有常量的 C# 泛型
【发布时间】:2011-06-05 21:50:19
【问题描述】:

有没有类似这个 C++ 模板的东西?

template <int A>
class B
{
    int f()
    {
        return A;
    }
}

我想让 B、B 等(例如元组)的每个实例都成为不同的类型。

【问题讨论】:

    标签: c# c++ security templates generics


    【解决方案1】:

    简短的回答是

    它不适合与 C++ 模板相对的 C# 泛型的工作方式。

    .net 泛型不是语言功能,它们是运行时功能。运行时知道如何从特殊的泛型字节码中实例化泛型,这与 C++ 模板可以描述的内容相比受到了很大的限制。

    将此与 C++ 模板进行比较,后者基本上使用替换类型来实例化类的整个 AST。可以将基于 AST 的实例化添加到运行时,但它肯定会比当前的泛型复杂得多。

    如果没有值类型数组(仅存在于不安全代码中)之类的功能,使用此类参数的递归模板实例化或模板特化也不会很有用。

    【讨论】:

    • 好吧,在运行时 (JIT) .Net 会进行实例化。实例化本质上是带有修改的复制。我不会像 MSIL 那样反对 AST。相似概念的不同表示。
    【解决方案2】:

    解决此限制的方法是定义一个类,该类本身会公开您感兴趣的文字值。例如:

    public interface ILiteralInteger
    {
       int Literal { get; }
    }
    
    public class LiteralInt10 : ILiteralInteger
    {
       public int Literal { get { return 10; } }
    }
    
    public class MyTemplateClass< L > where L: ILiteralInteger, new( )
    {
       private static ILiteralInteger MaxRows = new L( );
    
       public void SomeFunc( )
       {
          // use the literal value as required
          if( MaxRows.Literal ) ...
       }
    }
    

    用法:

    var myObject = new MyTemplateClass< LiteralInt10 >( );
    

    【讨论】:

    • 一个肮脏的解决方法,但很聪明。不幸的是,这是目前唯一的方法
    【解决方案3】:

    C# 不像 C++ 那样支持非类型泛型参数。

    C# 泛型比 C++ 模板更简单,功能也更差。 MSDN 有一个简洁的Differences Between C++ Templates and C# Generics 列表。

    【讨论】:

    • 但是,附加信息可以“编码”成类型(通过合同定义或其他方式),使用一组固定的谨慎类型很容易看到(例如Vn : V, where Vn.Value = n for all Vn),但也有@ 987654322@ -- 并不是我在这里推荐任何一种方法 :-)
    • 不是一个错误的答案,但如果它提供 C# 泛型的链接和/或讨论泛型声明/“实例化”与模板实例化,那就太好了。
    • @pst:当然;您可以将信息编码为类型。您还可以完全避免泛型并编写大量非泛型类。在任何情况下;我添加了指向 MSDN 文章的链接,该文章列举了模板和泛型之间的主要区别。
    【解决方案4】:

    C# 泛型是在运行时专门化的,而 C++ 模板在编译时被处理以创建一个全新的类型。鉴于此,运行时根本不具备处理非类型参数的功能(这不仅仅是 C# 问题)。

    【讨论】:

      【解决方案5】:

      以下方法可能有用,具体取决于您要完成的工作。底层集合的大小由抽象属性决定。

      public abstract class TupleBase
      {
          protected abstract int NumElems { get; }
          protected object[] values;
      
          protected TupleBase(params object[] init)
          {
              if (init.Length != NumElems)
              {
                  throw new ArgumentException($"Collection must contains {NumElems} elements", nameof(init));
              }
              values = new object[NumElems];
              for (int i = 0; i < init.Length; i++)
              {
                  values[i] = init[i];
              }
          }
      
          protected object Get(int idx) => values[idx];
          protected void Set(int idx, object value) => values[idx] = value;
      }
      
      public class Tuple1<T> : TupleBase {
          protected override int NumElems => 1;
          public Tuple1(T val0) : base(val0) { }
          public T Get0() => (T)Get(0);
          public void Set0(T value) => Set(0, value);
      }
      
      public class Tuple2<T, U> : TupleBase {
          protected override int NumElems => 2;
          public Tuple2(T val0, U val1) : base(val0, val1) { }
          public T Get0() => (T)Get(0);
          public U Get1() => (U)Get(1);
          public void Set0(T value) => Set(0, value);
          public void Set1(U value) => Set(1, value);
      }
      
      public class Tuple3<T, U, V> : TupleBase
      {
          protected override int NumElems => 3;
          public Tuple3(T val0, U val1, V val2) : base(val0, val1, val2) { }
          public T Get0() => (T)Get(0);
          public U Get1() => (U)Get(1);
          public V Get2() => (V)Get(2);
          public void Set0(T value) => Set(0, value);
          public void Set1(U value) => Set(1, value);
          public void Set2(V value) => Set(2, value);
      }
      
      public class Tuple4<T, U, V, W> : TupleBase
      {
          protected override int NumElems => 4;
          public Tuple4(T val0, U val1, V val2, W val3) : base(val0, val1, val2, val3) { }
      
          public T Get0() => (T)Get(0);
          public U Get1() => (U)Get(1);
          public V Get2() => (V)Get(2);
          public W Get3() => (W)Get(3);
          public void Set0(T value) => Set(0, value);
          public void Set1(U value) => Set(1, value);
          public void Set2(V value) => Set(2, value);
          public void Set3(W value) => Set(3, value);
      }
      
      public class Tuple5<T, U, V, W, X> : TupleBase
      {
          protected override int NumElems => 5;
          public Tuple5(T val0, U val1, V val2, W val3, X val4) : base(val0, val1, val2, val3, val4) { }
          public T Get0() => (T)Get(0);
          public U Get1() => (U)Get(1);
          public V Get2() => (V)Get(2);
          public W Get3() => (W)Get(3);
          public X Get4() => (X)Get(4);
          public void Set0(T value) => Set(0, value);
          public void Set1(U value) => Set(1, value);
          public void Set2(V value) => Set(2, value);
          public void Set3(W value) => Set(3, value);
          public void Set4(X value) => Set(4, value);
      }
      

      【讨论】:

        【解决方案6】:

        在 C# 10 中,有些东西接近了:

        interface IConstant
        {
            abstract static int Constant { get; }
        }
        
        class B<T> where T : IConstant
        {
            int GetConstant() => T.GetConstant();
        }
        
        class Constant1 : IConstant { static int Constant => 1; }
        class Constant2 : IConstant { static int Constant => 2; }
        

        这并不能保证Constant 实际上是恒定的,遗憾的是,您需要为要使用的所有数字定义类型。但是,它确实允许您区分 B&lt;Constant1&gt;B&lt;Constant2&gt;

        几乎不是等价物,但至少是某种东西

        请注意,您需要将 &lt;EnablePreviewFeatures&gt;true&lt;/EnablePreviewFeatures&gt; 添加到您的 .csproj 中才能正常工作,因为 abstract static 接口方法仍处于预览阶段

        【讨论】:

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