Lazy implementation and .NET generics

2019-03-24 16:49发布

I was looking for ways to do lazy initialization and found Lazy<T> which is included in .NET 4.

I was thinking of rolling my own implementation of Lazy<T> for .NET 3.5 (with a simpler multi-thread policy), and I bumped into the following problem:

Lazy has basically two types of constructors:

class Lazy<T> {

    public Lazy(){...} // ctor #1

which uses T's default constructor for creating an instance of T, and

    public Lazy(Func<T> func){...} // ctor #2

which lets the caller decide how the instance of T is created.

Now here's the problem:

If I want compile-time checking for the 1st ctor I will add a restriction

class Lazy<T> where T: new() {...}

at the class level. This will allow me to use new T() to create an instance; but this restriction is not necessary for the 2nd ctor, and worse, it also restricts the types I can use (to those with a default ctor)

If I want to be able to use any type with the 2nd ctor, I will not set any restriction, and in the 1st ctor will use reflection to make sure T does have a default ctor. This approach, however, will lack the compile-time check, and will only throw a runtime exception if the 1st ctor is used with the wrong type.

My question is: Can I get the best of both worlds?

Ideally, I would like to get the compile-time check for every use of ctor #1, but at the same time be able to use ctor #2 for types that don't have a default ctor.

How does the Microsoft implementation do this? (I don't readily have access to the .NET 4 sources or dlls).

EDIT: (After "Reflector-ing" the MS assembly)

I checked the reference implementation and it doesn't do compile-time checks.
It uses reflection for the 'default ctor' case, of course accompanied by the runtime exception if things go bad.

5条回答
【Aperson】
2楼-- · 2019-03-24 17:11

Why don't you just download the parallel extesions and install Lazy<T> for 3.5? Direct link

查看更多
Ridiculous、
3楼-- · 2019-03-24 17:13

You could use a static factory method instead of a overload to the constructor:

public class Lazy<T>
{
    public Lazy( Func<T> f ) { /*...*/ }

   public static Lazy<R> Default<R>() where R : T, new()
   {
       return new Lazy<R>( () => new R() );
   }
}

Now, this breaks compatibility (to some extent) with the .NET 4.0 version of Lazy<T>, but it does achieve compile time safety for both types of usage.

You could make this a bit cleaner by making the constructors for Lazy<T> protected internal, and provide a static factory class that you always use to create instances:

public static class Lazy {
    static Lazy<T> Create<T>( Func<T> ) { ... }
    static Lazy<T> Create<T>( ) where T : new() { ... }
}
查看更多
ら.Afraid
4楼-- · 2019-03-24 17:14

My question is: Can I get the best of both worlds?

No.

Basically you have no compile time checkable constraint.

查看更多
甜甜的少女心
5楼-- · 2019-03-24 17:29

I expect the inbuilt implementation simply uses Activator.CreateInstance<T> for simplicity. The cleanest way I can think of cheating this is with a separate factory:

// non-generic factory class with generic methods
public static class Lazy {
    public static Lazy<T> Create<T>() where T : new() {
        return Create<T>(() => new T());
    }
    public static Lazy<T> Create<T>(Func<T> ctor) { ... }
}
public class Lazy<T> { ... }
查看更多
放荡不羁爱自由
6楼-- · 2019-03-24 17:32

Something like this should work for you. We used this in our local code base for a year or so before moving to 4.0.

public class Lazy<T> {
  private Func<T> func;
  private T result;
  private bool hasValue;
  public Lazy(Func<T> func) {
    this.func = func;
    this.hasValue = false;
  }
  public T Value {
    get {
      if (!this.hasValue) {
        this.result = this.func();
        this.hasValue = true;
      }
      return this.result;
    }
  }
}
查看更多
登录 后发表回答