次の方法で共有


Cyrus likes Weak & Strong references

Then Cyrus decided he wanted to support weak references. A weak reference is one that the GC can decide to release if there are no other references to it.

For our LazyLoader, that would mean that you create the item when demand appears, and it may go away if it’s not needed, and then it’ll come back later if needed again. At that point, you really do have a cache.

To do this, Cyrus first built some cool classes to handle weak and strong references. You could think of IStrongReferenceas being the same as a regular reference in C# today.

interface IReference<A>

{

    A Value { set; }

    IOptional<A> Target { get; }

}

class StrongReference<A> : IReference<A>

{

    IOptional<A> target = None<A>.Instance;

    public IOptional<A> Target

    {

        get

        {

            return target;

        }

    }

    public A Value

    {

        set

        {

            target = new Some<A>(value);

        }

    }

}

class WeakReference<A> : IReference<A>

{

    System.WeakReference reference;

    public WeakReference()

    {

        Value = default(A);

    }

    public IOptional<A> Target

    {

        get

        {

            IOptional<A> target = (IOptional<A>)reference.Target;

            if (target == null)

            {

                return None<A>.Instance;

            }

            else

            {

                return target;

            }

        }

    }

    public A Value

    {

        set

        {

            reference = new WeakReference(new Some<A>(value));

        }

    }

}

Note that you can have a strong or weak reference to ‘null’, and that’s not the same thing has not having a reference as all. ‘null’ is a value, and None<> is not a value. Ahh, the power of IOptional<>.

One other thing Cyrus mentioned is that he might put these in a References namespace, and refer to them with FQNs:

          References.IReference

          References.Weak

          References.Strong

That reads well, but C# wasn’t really built for it, IMO. A question of style, I guess.

Comments

  • Anonymous
    May 07, 2004
    WeakReference<A>:Target seems a bit inconsistent with StrongReference<A>:Target.

    In one case you're relying on a null value to mean there is nothing, but in the other case you're using None<A>.Instance as the default.

    Also, it seems that after you initialized a reference (with the Value property), you never can revert to the original state.


    And why use properties in this case? Only the setter or the getter are actually implemented. Is this just to spare some parenthesis?
  • Anonymous
    May 07, 2004
    Julien, an essential invariant of the Target getter is that it never returns null. null is not a valid value for it. It must return either a None<> or a Some<>. Unfortunately, I can't guarantee that with the code in it's current form. Someone could implement IReference<>.Target to return a null value, and i should harden the LazyLoader code to take care of that.

    Note: You are correct. There is a bug in the WeakReference code. Let's look at StrongReference<>. In that case we see the invariant holds because target is initialized with None<>.Instance, and it can only be set to an instance of Some<> (in the Value setter). So we can see that Target can never return null.

    In WeakReference<> however we can see that when you instantiate the WeakReference<> we winn instantiate the reference to a "new WeakReference(new Some<A>(null));" which would be incorrect. Instead, we want to write this:

    public WeakReference()
    {
    reference = new WeakReference(None<A>.Instance);
    }
  • Anonymous
    May 07, 2004
    Julien, you also mentioned: "Also, it seems that after you initialized a reference (with the Value property), you never can revert to the original state. "

    That's correct, but it's by design. Think of a referene like a C# local. say I have:

    String s;

    It starts out uninitialized (it's not even null). Then once you initialize it that's it, you're done, you can never go back to being unintialized.

    In one of Jay's recent posts he talked about why we implemented "IOptional". Why it was necessary when you needed a sentinal value to indicate something when there wasn't a value available in your domain. This is true of references. How do you tell if something has been set to null, versus something that has never been set. I could keep around a boolean flag as well as the value, but then you see that that boolean was eliminated through the use of the Some<> and None<> types.
  • Anonymous
    May 07, 2004
    Julien, in response to your last comment: "And why use properties in this case? Only the setter or the getter are actually implemented. Is this just to spare some parenthesis? "

    The proper use of methods/properties is still vague to me and i don't have clear rules defining how i should use them. However, in this case properties seemed more natural. If you have a reference there were two (or maybe three) things I'd like to do with it:

    a) Get it's value
    b) Set it's value
    c) Check if it's value was set

    In method form that would have been written:

    A GetValue()
    void SetValue(A value)
    bool IsSet()

    Those looked a lot like properties to me. However, i decided to wrap a and c together into one call. This was essential in the WeakReference case where you might make a call to IsSet, get "true" and then fail when you called GetValue. In other words, i needed the action of IsSet/GetValue to be atomic, which is why it seemed natural to have just one call that returned the IOptional<>
  • Anonymous
    May 10, 2004
    What about implementing IOptional and IReference as structures instead of classes?

    Structs can't inherit from other structs, but they can implement interfaces, so it should work here.
    The idea is that in the case of IOptional, if you had coded it by hand you would just have added a boolean (bIsInitialized) next to the reference, without adding an extra indirection.
  • Anonymous
    May 10, 2004
    Julien:
    I'd like to hear more back from you on why you think struct would be more appropriate here then classes. What problem does that solve?

    Also, i initially started with an Optional<A> type that included the bool flag. However, i'm not a big fan of state. It clutters up your type and it makes your methods far more complicated than they need be. Inheritance solved this for me (much in the vein of Fowler's "Replace Type Code With Subclasses" refactoring). Instead of needing a large method that is checking lots of state, I have two distinct methods that make sense (to me). Asking a None<> for it's value just throws. Asking a Some<> for it's value just returns the value. With the bool system i'd also have to add a HasValue property (thus complicating the interface). This way I can just ask it "are you something? or are you nothing?" Maybe the names should have been Something<> and Nothing<> instead of Some<> and None<> :-)
  • Anonymous
    May 11, 2004
    I was just thinking in terms of memory locality. Having a class is an extra level of indirection/reference.
    It's probably wrong of me to think "optimization" without having any actual data... ;-)

    I agree that using inheritance instead of using a bool is cleaner, but it feels somewhat over-engineered, for some reason :-(
  • Anonymous
    May 11, 2004
    Is a type check costly? Does it involve any reflection?

    Maybe IOptional could have a extra method, to avoid testing on the type. None<>.HasValue would always return false, Some<>.HasValue would always return true, so inheritance is still used.
  • Anonymous
    May 11, 2004
    A type check is more costly than storing state and having a bool, but not as costly as reflection.

    Anyone feeling industrious enough to runs some tests?
  • Anonymous
    May 15, 2004
    To jay: Nope. If the implementation runs fast enough then i'm fine with it. If, however, it turns out that this is the bottleneck in some system then its something to come back and look at.

    The least expensive solution to this will most likely be written in hand tuned assembly.

    To julien: it certainly wouldn't hurt to add a HasValue property (except for the vtable size increase (which doesn't really bother me)). There's a tradeoff though. We're complicating the interface without adding any additional benefit (why add something that the language gives you for free?).

    I've worked on projects with the following code structure:

    class Base {
    bool IsDerived1() { ... }
    bool IsDerived2() { ... }

    Derived1 AsDerived1() { ... }
    Derived2 AsDerived2() { ... }
    }

    class Derived1 : Base { ... }
    class Derived2 : Base { ... }

    i.e. instead of using strong runtime typing, we built in subclassing programmatically instead of using what the language gave us.

    I'm very much against this style of programming.

    Also, i tend to not think of costs until i have to. I prefer simple conceptually clean implementations before spending on a lot of energy on what might only be a 1% gain.

    The last project i worked on had hotspots in 3 places. Fixing up those areas gave me back a 20x boost in performance. They weren't things that had leapt out at me while coding, and the places that i thought might be a problem didn't even show up as blips on the profiler analysis.

    Adding state and extra methods to sidestep the built in language constructs seems like like over-engineering to me :-)
  • Anonymous
    May 17, 2004
    I still like the use of a HasValue method, rather than the type.
    In O'Caml for example, a pattern matching would really make sense (with None and 'a Some). But altough C# supports the type checking, it doesn't seem natural (to me at least).

    Also, I understand your "anti-pattern" example, where the type system is kind of manually rebuilt, but I don't think "HasValue" falls in that trap.

    How would you test for "HasValue" if you had three types: None, Little and Lots ?
    What if you had two types for "nothing" and two types for "something"?...