Rozpoznanie typu obiektu, następnie stworzenie go.

0

Witam, mam taki jeden problem. Potrzebuję stworzyć listę obiektów na podstawie innej listy, ale program sam musi sobie poradzić jaki typ stworzyć :)

Wygląda to tak mniej więcej:


namespace TestWPF {
    /// <summary>
    /// Logika interakcji dla klasy MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        public List<Enemy> EnemyList = null;
        public List<Enemy> _EnemyList = null;

        public abstract class Enemy {

            private int _HP;

            public int HP {
                get { return _HP; }
                set { _HP = value; }
            }

            public Enemy(int HP) {

                this.HP = HP;
            }
        }

        public class Wolf : Enemy {

            public Wolf(int HP = 15)
                : base(HP) { }
        }

        public class Spider : Enemy {

            public Spider(int HP = 8)
                : base(HP) { }
        }


        public MainWindow() {

            InitializeComponent();

            EnemyList = new List<Enemy>();
            _EnemyList = new List<Enemy>();

            EnemyList.Add(new Wolf());
            EnemyList.Add(new Spider());
            EnemyList.Add(new Wolf());
            EnemyList.Add(new Wolf());
            EnemyList.Add(new Spider());


            _EnemyList.Add(new EnemyList[2].GetType());        // O Tutaj, to jest bzdura
        }

    }
}

Jest sobie lista potworków, i pogram będzie losował odpowiednie sztuki. I teraz muszę stworzyć listę _EnemyList która będzie przechowywała tych już wybranych przeciwników, ale ja nie wiem czy to Wolf czy to Spider czy to jeszcze co innego więc trzeba to rozpoznać :) Z tym że muszę stworzyć zupełnie nowe sztuki, żeby w indeksach powiedzmy 2,3,4 nie było tego samego wilka.

Szukałem coś o

Activator.CreateInstance

Ale nie wiem jak to ogarnąć :(

Z góry dziękuję za pomoc.

1
  1. Tego abstrakta oraz inne klasy, które po nim dziedziczą wyrzuć na zewnątrz, najlepiej każdą klasę do osobnego pliku;
  2. Nie potrzebujesz publicznego gettera i settera jedynie przypisującego i zwracającego wartość. Zamiast tego zrób po prostu: public int Hp { get; set; };
  3. Typ możesz rozpoznać zwykłym ifem np: if (enemy is Dog) { (enemy as Dog).Bark(); }
  4. Nie musisz pisać public List<Enemy> EnemyList = null;, bo to z definicji jest null. :)
  5. Poza tym co w punkcie 4: staraj się unikać pól publicznych na rzecz publicznych własności.
  6. Aha, no i zapomniałbym: nic nie stoi na przeszkodzie, żeby zamiast abstrakta zastosować tutaj interfejs.

Możesz zrobić coś w podobie:

using System;
using System.Linq;
using System.Collections.Generic;

namespace App
{
    interface IEnemy
    {
        uint Hp { get; }
    }

    class Rat : IEnemy
    {
        public uint Hp { get; private set; }

        public Rat(uint hp)
        {
            Hp = hp;
        }
    }

    class Conan : IEnemy
    {
        public uint Hp { get; private set; }

        public Conan()
        {
            Hp = 999;
        }
    }

    class Slave : IEnemy
    {
        public uint Hp { get; private set; }

        public Slave()
        {
            Hp = 10;
        }
    }

    static class Extensions
    {
        public static bool Has(this List<IEnemy> enemies, Type enemyType)
        {
            return enemies.Any(e => e.GetType() == enemyType);
        }

        public static int HowMany(this List<IEnemy> enemies, Type enemyType)
        {
            return enemies.Count(e => e.GetType() == enemyType);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var enemies = new List<IEnemy> { new Conan(), new Rat(10), new Rat(15), new Rat(10) };

            Console.WriteLine($"Has conan: {enemies.Has(typeof(Conan))} in number of: {enemies.HowMany(typeof(Conan))}");
            Console.WriteLine($"Has rats: {enemies.Has(typeof(Rat))} in number of: {enemies.HowMany(typeof(Rat))}");
            Console.WriteLine($"Has slaves: {enemies.Has(typeof(Slave))} in number of: {enemies.HowMany(typeof(Slave))}");
        }
    }
}
0

Jakbyś stosował się konwencji nazewniczych, to łatwiej byłoby to ogarnąć.

Ogólnie to chyba starczy Ci: _EnemyList.Add((Enemy)Activator.CreateInstance(EnemyList[2].GetType()));

A co do bzdur - czemu Wolf i Spider to klasy wewnętrzne w MainWIndow?

0

No jak? To mu przecież nie zadziała: _EnemyList.Add(EnemyList[2].GetType()); Jak chcesz konwertować Type do Enemy? O_o To jest lista przeciwników, a nie klas Type.

0

Ten kod to tylko skrawek programu, chciałem tu prosto pokazać o co chodzi, żeby nie zamotać. Jeśli chodzi o te klasy wewnątrz, szczerze mówiąc dopiero się uczę i nie zwróciłem nawet na to uwagi. Dziękuję są to bardzo cenne rady.

@grzesiek51114

  1. Nie potrzebujesz publicznego gettera i settera jedynie przypisującego i zwracającego wartość. Zamiast tego zrób po prostu: public int Hp { get; set; };

Będę tworzył jednostkę która będzie padać po kilku atakach, więc muszę jakoś przechowywać jej punkty życia a ten sposób działa :)

3.Typ możesz rozpoznać zwykłym ifem np: if (enemy is Dog) { (enemy as Dog).Bark(); }

Ten sposób by nawet działał.. tylko jeśli przeciwników będzie kilkadziesiąt to raczej mało eleganckie i optymalne rozwiązanie :) Ale jakieś koło ratunkowe jest hehe.

  1. Poza tym co w punkcie 4: staraj się unikać pól publicznych na rzecz publicznych własności.

Z tymi polami publicznymi.. właśnie wiem i coś czułem że lepiej się tego wystrzegać, podobnie jak zmiennych globalnych w C ale OK, przy okazji zapytam.

Załóżmy że mam licznik którym steruję za pomocą dwóch Buttonów. Jeden Button zwiększa licznik, drugi zmniejsza. Licznik musi więc być statyczny i mieć najlepiej zasięg globalny. Tak to może wyglądać:

namespace TestWPF_2 {
    /// <summary>
    /// Logika interakcji dla klasy MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        public int counter;

        public MainWindow() {

            InitializeComponent();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e) {

            counter++;
            MessageBox.Show(counter.ToString());
        }

        private void Button_Click_2(object sender, RoutedEventArgs e) {

            counter--;
            MessageBox.Show(counter.ToString());
        }
    }
}

Bardzo prosto. Ale nie powinno się tak robić prawda. W swoim projekcie stosuję taką alternatywę:

namespace TestWPF_2 {
    /// <summary>
    /// Logika interakcji dla klasy MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        public MainWindow() {

            InitializeComponent();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e) {

            cnt.counter++;
            MessageBox.Show(cnt.counter.ToString());
        }

        private void Button_Click_2(object sender, RoutedEventArgs e) {

            cnt.counter--;
            MessageBox.Show(cnt.counter.ToString());
        }
    }
}

public static class cnt {

    public static int counter = 0;

}

Jest to trochę bardziej uciążliwe bo jednak robi to samo a trzeba wpisywać na początku nazwę klasy.. Znacie może na to jakieś patenty? :)

A odnośnie tematu, @grzesiek51114 to pokombinuję z Twoim kodem dzięki.

Znalazłem jeszcze podobny wątek.. tutaj próbowali z dictionary stworzyć obiekt:

Wstawiam tak bo Link przenosi na mój wątek..

https://4programmers.net/Forum/C_i_.NET/270700-c_dictionary

Tutaj użyli tej klasy Activator o której pisałem na wstępie. Próbowałem analogicznie z listą ale błędy mi wyskakują, głównie że nie da się przekonwertować z Enemy do Type :/

1

Będę tworzył jednostkę która będzie padać po kilku atakach, więc muszę jakoś przechowywać jej punkty życia a ten sposób działa

No, a dlaczego publiczna własność wraz z get, set ma niby nie dzialać?

Licznik musi więc być statyczny i mieć najlepiej zasięg globalny.

Wcale nie musi być. Opakuj to w klasę zawierającą, nie wiem... listę przeciwników czy co tam chcesz i operuj na konkretnym obiekcie. Jak tak nawalisz staticów do byle działań to się później nie połapiesz.

Bardzo prosto. Ale nie powinno się tak robić prawda.

Jest dokładnie odwrotnie tylko zrób z tego prywatną składową. Jeżeli potrzebują tego inna okna to wsadź to do jakiejś klasy i przekazuj tę klasę konstruktorami. Unikniesz staticów: patrz mój punkt wyżej.

Jest to trochę bardziej uciążliwe bo jednak robi to samo a trzeba wpisywać na początku nazwę klasy.. Znacie może na to jakieś patenty?

Jeżeli potrzebujesz staticów to lepiej użyć kontenera IoC i klasy zarejestrowanej w nim jako singleton ale jeżeli jesteś bardzo początkujący to może Ci się to trochę mieszać; generalnie tak się robi. Z drugiej strony do tak małego projektu to się może nie opłacać.

Znalazłem jeszcze podobny wątek.. tutaj próbowali z dictionary stworzyć obiekt:...

Zrób po prostu fabrykę odpowiednich przeciwników i już. Najprościej, a nie pomiesza Ci się w głowie jeżeli dopiero zaczynasz naukę. Poczytaj o wzorcu projektowym fabryka. Generalnie masz klasę, która dostarcza Ci obiekty jakie chcesz. Warunki przekazujesz jako parametry metody tej klasy. Możesz przekazać zwyczajnie typ końcowy przeciwnika, a fabryka Ci to dostarczy.

Możesz zrobić np. tak:

// celowo klasa statyczna. Jasne... można to wsadzić do IoC itd... ale pytacz zaznaczył, że jest początkujący
static class EnemiesFactory
    {
        public static IEnemy CreateEnemy(string typeName)
        {
            var enemyTypes = new Dictionary<string, Type>
            {
                { "Conan",typeof(Conan) },
                { "Rat", typeof(Rat) },
                { "Slave", typeof(Slave) }
            };

            return (IEnemy)Activator.CreateInstance(enemyTypes[typeName]);
        }
    }

// wywołanie:
var conan = EnemiesFactory.CreateEnemy(nameof(Conan));

PS: I znowu dziesiątki edycji posta, bo coś się przypomniało.

0

Tak między czasie kombinowania zadziałał Activator i całkiem mi się podoba:


namespace TestWPF {
    /// <summary>
    /// Logika interakcji dla klasy MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        public List<Enemy> EnemyList = null;
        public List<Enemy> _EnemyList = null;


        public MainWindow() {

            InitializeComponent();

            EnemyList = new List<Enemy>();
            _EnemyList = new List<Enemy>();

            EnemyList.Add(new Wolf());
            EnemyList.Add(new Spider());
            EnemyList.Add(new Wolf());
            EnemyList.Add(new Wolf());
            EnemyList.Add(new Spider());


            var obj = (Enemy)Activator.CreateInstance(EnemyList[2].GetType(), EnemyList[2].HP);

            MessageBox.Show(obj.HP.ToString());

        }
    }
}


public abstract class Enemy {

    private int _HP;

    public int HP {
        get { return _HP; }
        set { _HP = value; }
    }

    public Enemy(int HP) {

        this.HP = HP;
    }
}

public class Wolf : Enemy {

    public Wolf(int HP = 15)
        : base(HP) { }
}

public class Spider : Enemy {

    public Spider(int HP = 8)
        : base(HP) { }
}


Będę musiał przetrawić te wszystkie informacje.. zapoznam się z fabrykami. Dzięki wielkie za pomoc @grzesiek51114 :D

0

Dobrze byłoby sprawdzać w takiej faktorce czy klucz istnieje w słowniku i jeżeli nie istnieje to zwracać jakiegoś tam przeciwnika, najsłabszego czy coś. Inaczej może polecieć Ci wyjątek i apka się wysypie.

// Zakładając, że konstruktory przeciwników są bezparametrowe:
static class EnemiesFactory
    {
        public static IEnemy CreateEnemy(string typeName = "")
        {
            var enemyTypes = new Dictionary<string, Type>
            {
                { "Conan",typeof(Conan) },
                { "Rat", typeof(Rat) },
                { "Slave", typeof(Slave) }
            };

            return (IEnemy)Activator.CreateInstance(enemyTypes.ContainsKey(typeName) ? enemyTypes[typeName] : typeof(Slave));
        }
    }

1 użytkowników online, w tym zalogowanych: 0, gości: 1