Home > database >  How to return new class instance from generic method based on given types?
How to return new class instance from generic method based on given types?

Time:05-02

I have two parent classes: Item and ItemData. From the first, I make items such as WeaponItem and from the second I make corresponding data like WeaponData.
Each item type takes the same type of data in its constructor (example: when creating WeaponItem it needs WeaponData).

I have the following method:

public static T CreateNewItem<T, DT>(ItemData data) where T : Item where DT : ItemData
{
    if (data == null) return null;
    if (data is DT dt)
    {
        //determine type of T
        Type itemType = typeof(T);
        if (itemType = typeof(WeaponItem))
        {
                //make a new weapon item...
                WeaponItem new_weapon = new WeaponItem(dt); //error!
                return new_weapon;
        }
        if (itemType = typeof(ArmorItem))
        {
                //make a new armor item...
        }
        //etc...
    }
    return null;
}

And I call it like so:

CreateNewItem<WeaponItem, WeaponData>(data);

Note: data is of type ItemData or some inherited type (eg. WeaponData).

This method needs two types: one for the Item and one for the ItemData. I have several items (weapons, armors etc...) and I create the item based on the type I give. I want to also check for the data which I do with: data is DT dt to get the WeaponData from the data I pass, or null if the cast fails.

When I try doing WeaponItem new_weapon = new WeaponItem(dt); I get the following error:

Argument 1: cannot convert from 'DT' to 'WeaponData' [Assembly-CSharp]csharp(CS1503)

I tried casting dt to WeaponData like so: WeaponItem new_weapon = new WeaponItem((WeaponData)dt); , but I get a similar error

Cannot convert type 'DT' to 'WeaponData' [Assembly-CSharp]csharp(CS0030)

Similarly, using data in place of dt produces errors.


Is it possible to solve this problem without checking the type every time?

CodePudding user response:

Your design is a bit counter intuitive to me, so let's start with an extremely simple version of the generic method and go from there. This code you can copy and compile.

You can also click this link and just hit the Run button.

I'd say the biggest advantage here is the generic method became one line of code. You can add as many items as you want and never change that method. Whereas your implementation gets more and more complex.

Code:

public class ItemData
{
    public string ItemName { get; init; }
}

public class WeaponData : ItemData { }

public class Item
{
    public ItemData Data { get; init; }

    public Item(ItemData data)
    {
        Data = data;
    }
}

public class WeaponItem : Item  
{
    public WeaponItem(WeaponData data) : base(data) { }

    public override string ToString() => Data.ItemName;
}

public static T CreateNewItem<T, TD>(TD data) 
    where T : Item 
    where TD : ItemData
{
    return (T)Activator.CreateInstance(typeof(T), data);
}

static void Main(string[] args)
{
    var bestWeaponDataEver = new WeaponData() { ItemName = "Dragon Slayer" };
    var amazingWeapon = CreateNewItem<WeaponItem, WeaponData>(bestWeaponDataEver);
    Console.WriteLine(amazingWeapon);
}

Output:

Dragon Slayer

Another big difference is you're accepting the specific type ItemData which should be the generic type DT. Otherwise why does DT even exist? This might have tripped you up.

public static T CreateNewItem<T, DT>(ItemData data) where T : Item where DT : ItemData

Should be this instead

public static T CreateNewItem<T, DT>(DT data) where T : Item where DT : ItemData

Note the constructor for WeaponItem only accepts WeaponData. So despite the simple generic method, you can't create a weapon with the base ItemData or CakeData or anything else.

If you're not sure how to expand on this comment and I'll try to help further.

  • Related