6

I am using a pre-built third party class library (think .NET Framework...) that contains a class Foo with the following members:

public sealed class Foo {
  private object _state;
  public Foo(object state) { _state = state; }
}

Foo does not contain a public _state setter.

In a specific scenario, I want to set the state of a Foo object to the object itself.

This will NOT compile:

Foo f;
f = new Foo(f);   // Compilation error: Use of unassigned local variable 'f'

Given the above prerequisites, is there any way that I can set the state of a Foo object to the object itself?


Rationale The Timer class in Windows 8.1 does not contain the Timer(AsyncCallback) constructor which assigned the state of the Timer object with the object itself. I am trying to port .NET code that contains this Timer constructor to a Windows 8.1 class library, so my concern is How do I pass the object itself to its state member? This issue is further outlined here, but I thought it would be more efficient to also pose the principal question above.

4

4 回答 4

2

Workaround:

var foo = new Foo(/*params*/);
var fieldInfo = foo.GetType().GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance);
fieldInfo.SetValue(foo , foo);
于 2013-10-31T11:56:09.103 回答
2

If I understand your intention correctly you want a timer whose callback references the timer itself.

Timer timer = null;
timer = new Timer(() => {
 timer.Stop(); //sample
});

Creating an object is done through the newobj instruction which atomically allocates and invokes the constructor. Without cooperation from the ctor you cannot get a reference to the unconstructed object. So there's no other way that either this approach, or reflection.

You can extract the above code into a helper method, make timer a local variable and then every timer callback will close over its own private and unchanging variable.

Timer CreateTimer(Action<Timer> callback) {
    Timer timer = null;
    timer = new Timer(() => {
     callback(timer);
    });
}
于 2013-10-31T13:07:46.583 回答
1

Just having Foo, this isn't possible. You may introduce a facade/proxy object and pass this to the constructor code, that way you can wire things up:

public class FooFacade  {
    private Foo foo;
    public void SetFoo(Foo f) { foo = f; }

    // for each property:
    public X Y { get { return foo.Y; } }
}

Then you can use this facade:

FooFacade ff = new FooFacade();
Foo f = new Foo(ff);
ff.SetFoo(f);

Of course this isn't what you wanted in the first place. The drawback of this attempt is that the state of the object is limited to it's public representation.

With reflection, just for completeness:

// create an uninitialized object of type Foo, does not call constructor:
var f = (Foo)FormatterServices.GetUninitializedObject(typeof(Foo));

// get field:
var stateField = typeof(Foo).GetField("_state", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);

// set value to instance itself, invoke on f:
stateField.SetValue(f, f);
于 2013-10-31T11:53:20.940 回答
0

So as I understand it, you don't want to pass the object itself as the constructor parameter; you want to pass a lambda that depends on the object. Would that be correct?

In that case, a not-pretty-but-workable workaround would be to add a layer of indirection:

Action callback = null;
var timer = new Timer(() => {
    if (callback != null)
        callback();
});
callback = () => {
    // do something
    // do something with timer
}

So you'd be passing a lambda to the constructor -- that lambda is valid at the point you call the constructor, although it's not valid to call it yet because it depends on the callback variable being set. Then you immediately set the callback variable to the implementation you want, which can now safely refer to timer.

If you're using a timer that fires on a background thread, then there's a possible race condition, where the timer could fire before you initialize your callback variable to a non-null value; hence the null check inside the lambda. For timers that fire on the UI thread, that race condition doesn't exist, and the null check could be omitted.

于 2013-10-31T12:44:10.457 回答