Rozpoznanie typu obiektu, następnie stworzenie go.

Odpowiedz Nowy wątek
2017-07-17 20:36
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.

edytowany 1x, ostatnio: Miccaldo, 2017-07-17 20:37

Pozostało 580 znaków

2017-07-17 20:53
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))}");
        }
    }
}
edytowany 7x, ostatnio: grzesiek51114, 2017-07-18 08:06
Spacyjka się wkradła, jędza jedna :) - grzesiek51114 2017-07-17 21:33
A czemu nie po prostu public Hp { get; private set; } tylko rozciągnąłeś to na 5 linijek? No i tak poza tym, to chyba nie rozwiązujesz problemu pytającego, bo on chciał losować. - somekind 2017-07-18 01:25
W sumie prawda. - grzesiek51114 2017-07-18 07:03
@somekind: A czemu nie po prostu... - ech... nawet sam o tym napisałem w swoim poście :) - grzesiek51114 2017-07-18 08:07

Pozostało 580 znaków

2017-07-17 21:25
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?

edytowany 2x, ostatnio: somekind, 2017-07-18 01:23

Pozostało 580 znaków

2017-07-17 21:29
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.

edytowany 1x, ostatnio: grzesiek51114, 2017-07-17 21:31
Tja, zapomniałem kluczowego elementu. - somekind 2017-07-18 01:22
W sumie ten post powinien być komentarzem. :P - somekind 2017-07-18 09:09
Moderacja 101: Kiedy ktoś zwraca Ci uwagę, zagroź mu banem :P. - msm 2017-07-18 13:26
Nie da się upokorzyć banem kogoś, kto ma peugeota. - somekind 2017-07-18 13:36
@somekind: Będziesz chciał kiedyś odwiedzić cygańskie osiedle, zobaczysz. - grzesiek51114 2017-07-18 13:39

Pozostało 580 znaków

2017-07-18 10:44
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 :/

edytowany 2x, ostatnio: Miccaldo, 2017-07-18 10:55

Pozostało 580 znaków

2017-07-18 11:07

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.

edytowany 10x, ostatnio: grzesiek51114, 2017-07-18 12:15

Pozostało 580 znaków

2017-07-18 12:19
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

edytowany 1x, ostatnio: Miccaldo, 2017-07-18 12:24

Pozostało 580 znaków

2017-07-18 12:31
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));
        }
    }
edytowany 1x, ostatnio: grzesiek51114, 2017-07-18 12:36

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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