Страницы

Поиск по вопросам

воскресенье, 24 ноября 2019 г.

Какой потомок вызвал статический метод родителя? или миссия невыполнима?


Я получил тестовое задание во время собеседования на должность C# программиста, но так и не смог его решить, т.к. не понял что именно требуется. 

Пишу это, т.к. хочу совершенствоваться, заканчивать все начатые дела, понимать 
знать то что до этого не понимал или не знал по мере возможности. 

Со словами "вот лёгенькое тестовое задание" интервьюер отправил мне email. Цитирую постановку:


  Классы А и В не включают реализации свойств, полей и методов, не атрибутированы и являются наследниками общего предка С
  
  1) не создавая экземпляров описанных классов, реализовать в классе C статически
метод с сигнатурой public static string GetName(), для которого истинно A.GetName() == "A" && B.GetName() == "B"
  
  2) реализовать единый счетчик количества экземпляров всех наследников класса 
с сигнатурой поля public static int Count в классе, не лежащем вне линии наследования


Я уточнил у интервьюера, что имеется в виду под константами "A" и "B" - он ответил
что это именно имена классов. Т.е. метод GetName() должен возвращать имя класса потомка.

В процессе тщетных попыток реализовать в точности как описано в задании и яростного гугления был найден материал со stackoverflow: 


How to get the class Type in a base class static method in .NET?
Get inherited caller type name in base static class


Где, на сколько я понял, утверждается, что получить имя класса потомка из статического метода невозможно без хаков.

Я прикидывал ещё несколько вариантов (например, создание нового класса, который н
описан в задании), но все они связаны с попытками обхода условий, что не вяжется со словами интервьюера о том, что есть конкретное решение, думаю оно должно быть честное и простое, раз он так вначале утверждал.

У меня получилось сделать только это, но я знаю, что это неверно, т.к. метод GetName не статический и создаются экземпляры класса:

1.

using System;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine ( new A().GetName() == "A" && new B().GetName() == "B" );
        }
    }
    class C {
        public string GetName() {
            return this.GetType().Name;
        }
    }
    class A : C{}
    class B : C{}
}                



2.

using System;

namespace Rextester
{
    public class Program
    {                
        public static void Main(string[] args)
        {
            var a = new A();
            var b = new B();
            var c = new C();
        }
    }
    public class C {
        public static int Count;

        public C () {

            if ( this is A
               || this is B )
                Count++;
            Console.WriteLine ( "Count is: " + Count );
        }
    }
    public class A : C{}
    public class B : C{}
}


Ответ интервьювера:


  А:добрый день, я вижу, однако вынужден огорчить, задача имеет конкретное решение
  
  Б:Т.е. ни один из двух пунктов неверный?
  
  А:второй зависит от первого
  
  А:и соотвественно, первый не верен, так как создавать экземпляров нельзя


Пожалуйста, помогите мне решить эту задачу. Мне кажется, что в самой постановке есть противоречия, тем более в ней используется двойное отрицание, что трудно для понимания.

Обновлено:
Коллеги, благодаря вашим ответам, пояснениям и замечаниям мне всё-таки удалось решит
задачу так: создал ещё один класс C, который сделал базовым для всех и установил в нём счётчик. Новый класс обеспечил выполнение условия "А и B являются наследниками общего предка С".

using System;
namespace Rextester
{
    public class Program
    {
        public static void Main ( string[] args )
        {
            Console.WriteLine ( A.GetName() == "A" 
                               && B.GetName() == "B" );

            new A (); // 1 
            new B (); // 2
            new C (); // 3
            new A (); // 4
            new B (); // 5
        }
    }

    class C {
        public static int Count;
    }

    class C < T > : C where T: C < T > {

        public C () { 

            if ( this is A
               || this is B )

                Count++;
            Console.WriteLine ( "Count is:" + Count );
        }

        public static string GetName(){
            return typeof ( T ).Name;
        }
    }

    class A : C < A > { }
    class B : C < B > { }

}


Благодаря ответам и замечаниям иностранных коллег, этот код я дополнил соответствующим
модификаторами: базовый класс C и C < T > пометил модификатором abstract, определяющи
данные классы как базовые, а поле Count дополнил геттером и сеттером ( с модификатором доступа protected ), для ограничения доступа к переменной. Так же конструктор базового класса C помечен модификатором доступа protected. Допиленный вариант решения:

using System;
namespace Rextester
{
    public class Program
    {
        public static void Main ( string[] args )
        {
            Console.WriteLine ( A.GetName() == "A" 
                               && B.GetName() == "B" );

            new A (); 
            new B ();
        }
    }

    abstract class C {
        public static int Count { get; protected set;}
    }

    abstract class C < T > : C where T: C < T > {

        protected C () { 

            if ( this is A
               || this is B )

                Count++;
            Console.WriteLine ( "Count is:" + Count );
        }

        public static string GetName(){
            return typeof ( T ).Name;
        }
    }

    class A : C < A > { }
    class B : C < B > { }

}


Спасибо всем, кто участвовал в решении этой головоломки! Вы потрясающие люди)
    


Ответы

Ответ 1



Как сказано в ответах по ссылке. В простейшем случае class C { public static string GetName() => ... } class A : C {} class B : C {} В методе GetName нельзя узнать тип наследника, просто потому, что статические методы не переопределяются, и в IL будет стоять не call A.GetName а call C.GetName Так как ограничений на класс C нет, его можно сделать generic классом. Наследник будут специфицировать generic параметр собой и в этом случае базовый класс может выглядеть так: class C { public static string GetName() => typeof(T).Name; } Наследники: class A : C { } class B : C { } В этом случае: A.GetName() вернет "A", B.GetName() вернет "B",

Ответ 2



Тут Jon Skeet пишет, что в рантайме узнать тип класса-наследника через статически метод базового класса невозможно, поскольку в процессе компиляции в IL (надеюсь я правильно выражаюсь) пишется тип класса, который его реализует, т.е. о наследнике какая-либо информация попросту отсутствует.

Ответ 3



В рамках наркомании, вот этот пример пройдет проверочный кейс: public class C { private static bool isAreturned = false; public static string GetName() { isAreturned = !isAreturned; return isAreturned ? "A" : "B"; } } public class A : C { } public class B : C { }

Ответ 4



На первый вопрос отправил бы такой код, потом бы мог поспорить какое из условий задания я не выполнил! public class D { private string i = string.Empty; public D() { val = i; } public string val; public static bool flag = false; public static implicit operator string(D a) { if (!flag) { flag = true; return "A"; } return "B"; } } public class C :D { public static bool Istina() { return A.GetName() == "A" && B.GetName() == "B"; } public static string GetName() { return new D(); } } public class A : C { } public class B : C { }

Комментариев нет:

Отправить комментарий