【发布时间】:2011-01-16 09:30:53
【问题描述】:
我正在编写一个“监视器”对象来帮助调试我的应用程序。这个 Monitor 对象可以在运行时从 IronPython 解释器访问。我的问题是,是否可以在 C# 中存储对值类型的引用?假设我有以下课程:
class Test
{
public int a;
}
我可以以某种方式存储指向“a”的“指针”以便能够随时检查它的值吗?是否可以使用安全和托管的代码?
谢谢。
【问题讨论】:
我正在编写一个“监视器”对象来帮助调试我的应用程序。这个 Monitor 对象可以在运行时从 IronPython 解释器访问。我的问题是,是否可以在 C# 中存储对值类型的引用?假设我有以下课程:
class Test
{
public int a;
}
我可以以某种方式存储指向“a”的“指针”以便能够随时检查它的值吗?是否可以使用安全和托管的代码?
谢谢。
【问题讨论】:
您不能在字段或数组中存储对变量的引用。 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参数有用,对其他参数也有用(因此代码可以将对可变类对象的引用传递给方法,并且知道......
不 - 您不能直接在 C# 中存储指向值类型的“指针”。
通常,您会持有对包含“a”的测试实例的引用 - 这使您可以访问a(通过testInstance.a)。
【讨论】:
这是我想出的一个模式,我发现自己经常使用它。通常在将属性作为参数传递以用于父类型的任何对象的情况下,但它也适用于单个实例。 (不适用于本地范围值类型)
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(不要忘记首先检查空值)
【讨论】:
您可以创建 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;
}
【讨论】:
您可以使用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
}
}
【讨论】:
Foo* 类型。
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 标签是另一个。开始使用它们!
【讨论】: