简体   繁体   中英

Creating a new instance of an object each time method is called

Messenger.Default.Register<OpenWindowMessage>(this, message =>
{
    var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>();
    adventurerWindowVM.Adv = message.Argument;
    var adventurerWindow = new AdventurerView() 
    {
        DataContext = adventurerWindowVM
    };
    adventurerWindow.Show();
});

This code is fairly simple; it just opens a new window and sets the DataContext of the new window. The problem I'm having is that if I execute this twice, the content of the first instance will be overwritten and be set to that of the second since adventurerWindowVM is the DataContext of both windows and it is overwritten each time this code is called. I'm looking for a way to prevent this; I'd like to be able to open multiple windows using this message and have each of them be unique, but thus far I haven't figured out a way to do so. Any advice would be greatly appreciated. I apologize for the vague title; I was unsure of what to name this question. (Also, I know that this isn't a method. What would this block of code be called?)

Update: I'm using MVVM Light and my code is based off of an example somebody provided for me in this answer: https://stackoverflow.com/a/16994523/1667020

Here is some code from my ViewModelLocator.cs

public ViewModelLocator()
{
    _main = new MainViewModel();

    ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
    SimpleIoc.Default.Register<GameViewModel>();
    SimpleIoc.Default.Register<AdventurerViewModel>();
}

Having given the other answer, I guess I can say the IoC container used here is just SimpleIoC from MvvmLight and to get a new instance of the VM on every GetInstance(...) all you need to do is pass in a unique key every time when trying to resolve an instance of the VM.

So you can switch

var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>();

to

var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>(System.Guid.NewGuid().ToString());

However as mentioned by the author of MVVMLight Here these VM's will get cached and we need to get rid of them when no longer needed. In your case probably when the Window is closed.

Thus I'd have that entire lambda something like:

Messenger.Default.Register<OpenWindowMessage>(this, message =>
{
    var uniqueKey = System.Guid.NewGuid().ToString();
    var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>(uniqueKey);
    adventurerWindowVM.Adv = message.Argument;
    var adventurerWindow = new AdventurerView() 
    {
        DataContext = adventurerWindowVM
    };
    adventurerWindow.Closed += (sender, args) => SimpleIoc.Default.Unregister(uniqueKey);
    adventurerWindow.Show();
});

Note:

While this is somewhat longer 3 lines compared to just creating a new VM yourself with ( new AdventurerViewModel() ) I still favor this because if you use an IoC container to manage LifeTime of your VM's, then have it manage them completely. Don't really like mix-n-match when not needed. Rather keep the IoC Container doing what it's meant to do.

If you need more control over VM injection and Life-time management look at more sophisticated Ioc controllers such as Unity . SimpleIoC was just meant to be a simple get your feet "wet" in IoC kind of container and it does a very good job in that regard.

I think you are trying to use the same instance of your ViewModel with multiple views. So the views will obviously overwrite each others viewmodel contents.

What if you do this;

        Messenger.Default.Register<OpenWindowMessage>(this, message =>
    {
        var adventurerWindowVM = new AdventurerViewModel();
        adventurerWindowVM.Adv = message.Argument;
        var adventurerWindow = new AdventurerView() 
        {
            DataContext = adventurerWindowVM
        };
        adventurerWindow.Show();
    });

It's a method call, passing in an anonymous method using a lambda expression.

It looks like you are getting your AdventurerViewModel from some sort of IoC container. How is the IoC container configured? In particular, what is the scope of the objects it gives you back? If you have the IoC configured to create objects in singleton scope, for example, then you will always get back a reference to the same object each time. You may need to configure the scope of the object in your IoC container so that it gives you back a fresh copy every time.

How you do that will depend on your IoC container. Without knowing which IoC framework you are using or seeing its configuration, it's impossible to make any further comment.

My advice would be to create an extension method for SimpleIOC. Something like this:

public static T CreateInstance<T>(this SimpleIoc simpleIoc)
{
    // TODO implement
}

You already know the method to get the same instance; extended SimpleIoc with a method to create a new instance:

T instance = SimpleIoc.Default.GetInstance<T>();
T createdInstance = SimpleIoc.Defalt.CreateInstance<T>();

If you are not familiar with extension methods, see Extension Methods Demystified

The implementation:

  • Of type T, get the constructor.
  • If there is more than one constructor: either throw exception, or decide which constructor to use. Simple method: use the same method that is used in SimpleIoc.GetInstance , with an attribute. More elaborate method: try to find out if you can find registered elements that match one of the constructors. This is not explained here.
  • Once you've found the constructor that you need, get its parameters.
  • Ask SimpleIoc for instances of this parameter, or if they should be new also, ask SimpleIoc to create new instances.
  • CreateInstance

.

public static T CreateInstance<T>(this SimpleIoc ioc)
{
    return (T)ioc.CreateInstance(typeof(T));
}

public static object CreateInstance(this SimpleIoc ioc, Type type)
{
    ConstructorInfo constructor = ioc.GetConstructor(type);
    IEnumerable<object> constructorParameterValues = ioc.GetParameters(constructor);
    constructor.Invoke(constructorParameterValues.ToArray());
}

To decide which constructor to use:

private static ConstructorInfo GetConstructor(this SimpleIoc ioc, Type type)
{
    ConstructorInfo[] constructors = type.GetConstructors();
    ConstructorInfo constructorToUse;
    if (constructorInfo.Length > 1)
    {
        // Decide which constructor to use; not explained here
        // use Attribute like SimpleIoc.GetInstance?
        // other method: use SimpleIoc.IsRegistered to check which Parameters
        // are registered: use ConstructorInfo.GetParameters()
        constructorToUse = 
    }
    else
        constructorToUse = constructoInfo[0];
    return constructorToUse;
}

To get the values of the parameters in the constructor, we need to decide whether we want existing values from Ioc, or create new values:

public static IEnumerable<object> GetParameterValues(this simpleIoc ioc,
    ConstructorInfo constructor)
{
    IEnumerable<Type> parameterTypes = contructor.GetParameters()
        .Select(parameter => parameter.ParameterType);
    return ioc.GetInstances(parameterTypes);
}

public static IEnumerable<object> GetInstances(this SimpleIoc ioc,
    IEnumerable<Type> types)
{
    // TODO: decide if we want an existing instance from ioc,
    // or a new one

    // use existing instance:
    return types.Select(type => ioc.GetInstance(type));

    // or create a new instance:
    return types.Select(type => ioc.CreateInstance(type));
}

This seems like a lot of code, but most of it is comment and most methods are one liners.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM