【问题标题】:Store a reference to a value type?存储对值类型的引用?
【发布时间】:2011-01-16 09:30:53
【问题描述】:

我正在编写一个“监视器”对象来帮助调试我的应用程序。这个 Monitor 对象可以在运行时从 IronPython 解释器访问。我的问题是,是否可以在 C# 中存储对值类型的引用?假设我有以下课程:

class Test
{
    public int a;
}

我可以以某种方式存储指向“a”的“指针”以便能够随时检查它的值吗?是否可以使用安全和托管的代码?

谢谢。

【问题讨论】:

    标签: c# reference


    【解决方案1】:

    您不能在字段或数组中存储对变量的引用。 CLR 要求对变量的引用位于 (1) 形式参数、(2) 本地或 (3) 方法的返回类型中。 C# 支持 (1) 但不支持其他两个。

    (旁白:可能 C# 支持其他两个功能;事实上,我已经编写了一个原型编译器来实现这些功能。它非常简洁。(有关详细信息,请参阅http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/。 ) 当然,必须编写一个算法来验证没有 ref local 可能引用现在已销毁的堆栈帧上的本地,这有点棘手,但它是可行的。也许我们会在假设中支持这一点该语言的未来版本。(更新:它已添加到 C# 7!))

    但是,您可以通过将变量放在字段或数组中来使变量具有任意长的生命周期。如果您需要的是“我需要将别名存储到任意变量”意义上的“参考”,那么,不。但是,如果您需要的是“我需要一个可以让我读取和写入特定变量的魔法令牌”意义上的引用,那么只需使用一个委托或一对委托。

    sealed class Ref<T> 
    {
        private Func<T> getter;
        private Action<T> setter;
        public Ref(Func<T> getter, Action<T> setter)
        {
            this.getter = getter;
            this.setter = setter;
        }
        public T Value
        {
            get { return getter(); }
            set { setter(value); }
        }
    }
    ...
    Ref<string> M() 
    {
        string x = "hello";
        Ref<string> rx = new Ref<string>(()=>x, v=>{x=v;});
        rx.Value = "goodbye";
        Console.WriteLine(x); // goodbye
        return rx;
    }
    

    外部局部变量 x 将至少在 rx 被回收之前保持活动状态。

    【讨论】:

    • 为了使其正常工作,语言/框架应允许将存储位置声明为持久的、可返回的或短暂的,并且只允许将一个存储类的东西存储到相同或更多的存储位置限制类。函数的返回值将是其声明的返回类或传递给其“可返回”参数的最具限制性的存储类。这样的规则不仅对ref参数有用,对其他参数也有用(因此代码可以将对可变类对象的引用传递给方法,并且知道......
    • ...任何由此产生的突变都将在相关方法返回之前完成,因此在许多情况下消除了防御性复制的需要)。
    • +1 你的答案,因为它解决了持久性问题。不过,我有点难过,C# 没有将值类型存储为引用的机制。我确实理解这可能会使 GC 变得复杂,但引用值类型确实会对编码技术产生很大影响。
    • @DJPeterson:你可以存储一个值类型作为参考;这在 C# 中称为 boxing。我认为您的意思正好相反:C# 没有将对变量的引用 存储为字段值 的机制。
    • @Eric Lippert 这是正确的(如果我理解正确的话)。拳击是我针对我的情况进行的调查,但发现缺乏类型安全和空检查是无法忍受的。我可以使用包装器在一定程度上解决这个问题。然而,交易破坏者是这样一个事实,即不存在用于在字段内保存对值类型的引用同时保留类型信息的构造。没有它,我无法在没有装箱的情况下安全地返回引用,这当然将类型安全的负担放在了消费者而不是值的提供者身上——完全违背了目的。
    【解决方案2】:

    不 - 您不能直接在 C# 中存储指向值类型的“指针”。

    通常,您会持有对包含“a”的测试实例的引用 - 这使您可以访问a(通过testInstance.a)。

    【讨论】:

    • 但是我想没有办法动态存储一些东西吗?喜欢将它包装在委托或类似的东西中?
    • 您可以随时复制它。你可以做一个闭包,但是(间接地)存储对包含类的引用......
    • 我一直在网上寻找如何做到这一点。请您编辑您的答案显示一个小例子好吗?现在我正在使用反射,但它提供了像 Monitor.Watch(new Property(Game, "FPS")) 这样的东西,我宁愿只做 Monitor.Watch(Game.FPS) 或者至少更简单的东西.
    • 你能展示一些代码来准确地展示你的(当前)代码是如何工作的吗?我建议发布(作为一个新问题),征求有关如何改进它的建议(即:删除魔术字符串等)。
    • 事实上,在底层 CLR 中不可能有“ref”字段。对允许引用基于堆栈的变量的系统执行验证会困难得多。 C# 对此无能为力。
    【解决方案3】:

    这是我想出的一个模式,我发现自己经常使用它。通常在将属性作为参数传递以用于父类型的任何对象的情况下,但它也适用于单个实例。 (不适用于本地范围值类型)

    public interface IValuePointer<T>
    {
        T Value { get; set; }
    }
    public class ValuePointer<TParent, TType> : IValuePointer<TType>
    {
        private readonly TParent _instance;
        private readonly Func<TParent, TType> _propertyExpression;
        private readonly PropertyInfo _propInfo;
        private readonly FieldInfo _fieldInfo;
    
        public ValuePointer(TParent instance, 
                            Expression<Func<TParent, TType>> propertyExpression)
        {
            _instance = instance;
            _propertyExpression = propertyExpression.Compile();
            _propInfo = ((MemberExpression)(propertyExpression).Body).Member as PropertyInfo;
            _fieldInfo = ((MemberExpression)(propertyExpression).Body).Member as FieldInfo;
        }
    
        public TType Value
        {
            get { return _propertyExpression.Invoke(_instance); }
            set
            {
                if (_fieldInfo != null)
                {
                    _fieldInfo.SetValue(_instance, value);
                    return;
                }
                _propInfo.SetValue(_instance, value, null);
            }
        }
    }
    

    然后就可以这样使用了

    class Test
    {
        public int a;
    }
    void Main()
    {
        Test testInstance = new Test();
        var pointer = new ValuePointer(testInstance,x=> x.a);
        testInstance.a = 5;
        int copyOfValue = pointer.Value;
        pointer.Value = 6;
    }
    

    注意带有一组更有限的模板参数的接口,这允许您将指针传递给不知道父类型的东西。

    您甚至可以实现另一个没有模板参数的接口,该接口在任何值类型上调用 .ToString(不要忘记首先检查空值)

    【讨论】:

      【解决方案4】:

      您可以创建 ref-return 委托。这类似于 Erik 的解决方案,除了它使用单个 ref-returning 委托而不是 getter 和 setter。

      您不能将它与属性或局部变量一起使用,但它会返回真正的引用(不仅仅是复制)。

      public delegate ref T Ref<T>();
      
      class Test
      {
          public int a;
      }
      
      static Ref<int> M()
      {
          Test t = new Test();
          t.a = 10;
      
          Ref<int> rx = () => ref t.a;
          rx() = 5;
          Console.WriteLine(t.a); // 5
          return rx;
      }
      

      【讨论】:

        【解决方案5】:

        您可以使用usafe 代码直接获取指向值类型的指针

        public class Foo
        {
            public int a;
        }
        
        unsafe static class Program
        {
            static void Main(string[] args)
            {
                var f=new Foo() { a=1 };
                // f.a = 1
        
                fixed(int* ptr=&f.a)
                {
                    *ptr=2;
                }
                // f.a = 2
            }
        }
        

        【讨论】:

        • 这仅适用于内在数字类型。 CLR 不允许 Foo* 类型。
        【解决方案6】:
        class Test
        {
            private int a;
        
            /// <summary>
            ///  points to my variable type interger,
            ///  where the identifier is named 'a'.
            /// </summary>
            public int A
            {
                get { return a; }
                set { a = value; }
            }
        }
        

        为什么要费尽心思编写复杂的代码,在各处声明链接到同一个位置的标识符?创建一个属性,添加一些 XML 代码来帮助您在课堂之外,并在您的编码中使用这些属性。

        我不知道存储指针,不认为这是可能的,但如果你只是想检查它的值,据我所知,最安全的方法是创建变量的属性。至少这样你可以随时检查它的属性,如果变量是静态的,你甚至不必创建类的实例来访问变量。

        房产有很多优势;类型安全是一个,XML 标签是另一个。开始使用它们!

        【讨论】:

        • 这似乎无法回答问题。提出的问题包括一个公共变量,它与您建议的公共财产几乎相同。
        • 表情是聊天客户端的习惯。不知道你说的脏话是什么意思,我只是表达我的想法。据我了解,他想要一个指向变量的标识符,如果可能的话,他将不得不(据我所知)将变量或访问它的东西公开。我说,好吧,为什么不直接使用属性呢?在 MAIN 或任何地方创建他的类“监视器”的实例时,使用该属性来检查其值。或使其成为静态并像这样访问该属性; namespace.class.property,当他需要检查值时。另一种选择。
        猜你喜欢
        • 1970-01-01
        • 2014-02-11
        • 2011-04-21
        • 1970-01-01
        • 1970-01-01
        • 2015-04-29
        • 1970-01-01
        • 2019-04-19
        • 1970-01-01
        相关资源
        最近更新 更多