Статически определить член, используя динамически определенный тип класса - PullRequest
4 голосов
/ 02 ноября 2011

Я пытаюсь создать делегат события, в котором параметр строго типизирован для соответствия текущему классу, например:

public class HPCRequest
{
    public delegate void RequestCompleteHandler(HPCRequest request);
    public event RequestCompleteHandler RequestComplete;

Проблема в том, что точка этого класса должна быть унаследована,и что я действительно хочу, чтобы все эти наследующие классы имели событие «RequestComplete», где делегат набирается для этого класса:

public class HPCGetConfig : HPCRequest
{
    //I want this class to effectively inherit something like this:
    //public delegate void RequestCompleteHandler(HPCGetConfig request);

Это потому, что в настоящее время, когда у меня есть функция, которая обрабатывает одиниз моих событий "RequestComplete", я в настоящее время должен сделать это:

    myGetConfigRequest.RequestComplete += new HPCRequest.RequestCompleteHandler(HPCGetExpectedLosses_RequestComplete);

void HPCGetConfig_RequestComplete(HPCRequest request)
{
    HPCGetConfig thisRequest = request as HPCGetConfig;
    //This should be strongly typed in the first place.

Но я хочу иметь возможность сделать что-то вроде этого:

    request.RequestComplete += new HPCGetConfig.RequestCompleteHandler(HPCGetConfig_RequestComplete);
    request.SendRequestAsync();
}

void HPCGetConfig_RequestComplete(HPCGetConfig request)
{
    request.RequestComplete -= HPCGetConfig_RequestComplete;

Попытки

Я пробовал это:

public delegate void RequestCompleteHandler<T>(T request) where T : HPCRequest; 
public event RequestCompleteHandler<T> RequestComplete;

, но когда я пытаюсь вызвать событие из базового класса, используя RequestComplete(this);, я получаю ошибку времени компиляции:`Delegate 'RequestCompleteHandler' имеет несколько недопустимых аргументов.

Это происходит независимо от того, настроил ли я весь класс HPCRequest как HPCRequest<T>, выполнив:

public class HPCRequest<T> where T : HPCRequest<T> 
{
    public delegate void RequestCompleteHandler<T>(T request); 
    public event RequestCompleteHandler<T> RequestComplete;

public class HPCGetConfig : HPCRequest<HPCGetConfig> { ...

Произошла та же ошибкакогда я пытаюсь вызвать событие: RequestComplete(this);

Я также попробовал все формы создания делегата и события и переопределения их, например, при выполнении:

public class HPCRequest
{
    public delegate void RequestCompleteHandler(HPCRequest request);
    public virtual event RequestCompleteHandler RequestComplete;


public sealed class HPCGetConfig : HPCRequest
{
    public delegate void RequestCompleteHandler(HPCGetConfig request);
    public override event RequestCompleteHandler RequestComplete;

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

Любые другие идеи?


Редактировать

Шаблонирование всего класса HPCRequestэто не вариант, после очень тщательной попытки я вижу, что он просто облажает каждую попытку использовать тип HPCRequest в качестве заполнителя для любого типа запроса.Если это решение будет работать, класс HPCRequest должен иметь возможность создания экземпляра и наследования без указания параметра типа.Мне нужно решение, которое не требует шаблонов HPCRequest.

Чтобы убедиться, что все точно знают, как я пытаюсь это использовать, я вставил пример кода в pastebin, который должен позволить вам поэкспериментировать сспособы заставить это событие работать, не нарушая ничего.Вот оно: http://pastebin.com/bbEYgLj1

Ответы [ 6 ]

3 голосов
/ 02 ноября 2011

Что вы можете попробовать:

public abstract class HPCRequest<T> where T : HPCRequest<T>
{
  public delegate void RequestCompleteHandler(T request);
  public event RequestCompleteHandler RequestComplete;

  protected void RaiseRequestComplete(T request)
  {
    if (RequestComplete != null)
    {
      RequestComplete(request);
    }
  }
}

public class Foo : HPCRequest<Foo>
{
  public void Bar()
  {
    RaiseRequestComplete(this);
  }
}

public class Example
{
  public static void Test()
  {
    var request = new Foo();

    request.RequestComplete += RequestComplete;
  }

  static void RequestComplete(Foo request)
  {
    // It's a Foo!
  }
}

Это само-ссылочное родовое ограничение позволяет, я думаю, то, что вы хотите. Я добавил protected RaiseRequestCompleted, чтобы вы могли вызывать событие из классов, которые наследуются от HCPRequest. В противном случае, только HCPRequest будет разрешено сделать это.

ОБНОВЛЕНИЕ : я обновил код для передачи this и добавил пример кода, который соответствует желаемому результату.

2 голосов
/ 02 ноября 2011

Вы можете ограничить параметр типа абстрактного класса производным от самого абстрактного класса.Это кажется странным, но вы можете настроить его так:

public abstract class HPCRequest<T> where T : HPCRequest<T>
{
    public delegate void RequestCompleteHandler(T request);
    public event RequestCompleteHandler RequestComplete;
}

public class DerivedHPCRequest : HPCRequest<DerivedHPCRequest>
{

}
2 голосов
/ 02 ноября 2011

Для этих целей можно использовать параметр универсального типа, поэтому каждый класс, унаследованный от HPCRequest, должен указывать параметр универсального типа. Также я бы предложил пометить классы, которые предназначены для использования в качестве базового класса с помощью модификатора abstract, чтобы избежать явной реализации:

public abstract class HPCRequest<TRequest>
  where TRequest: class
{
    public delegate void RequestCompleteHandler(TRequest request);
    public event RequestCompleteHandler<TRequest> RequestComplete;
}


public sealed class HPCGetConfig : HPCRequest<HPCGetConfig>
{
}
1 голос
/ 13 ноября 2011

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

Как примечание, нет реальной причины объявлять делегатов. Для соответствия стандартам MSDN Event Design ваше решение будет использовать EventHandler<T> и пользовательскую реализацию EventArgs.

код

//  this is a sample program using the pattern i am recommending
//  I'm pretty sure this is what you wanted your code to look like? 
public class Program
{
    public static void Main()
    {
        var request = new HPCGetConfig();
        request.RequestComplete += HandleConfigRequestCompleted;
        request.SendAsync();
    }

    static void HandleConfigRequestCompleted(object sender, RequestCompleteEventArgs<HPCGetConfig> e)
    {
        var request = e.Request;
        // do something with the request
    }
}

//  the non-generic event args class 
public abstract class RequestCompleteEventArgs : EventArgs
{
    public abstract Type RequestType { get; }
    public abstract object RequestObject { get; set; }
}

// the generic event args class
public class RequestCompleteEventArgs<T> : RequestCompleteEventArgs
{
    private T m_Request;


    public T Request
    {
        get { return m_Request; }
        set { m_Request = value; }
    }

    public override Type RequestType
    {
        get { return typeof(T); }
    }

    public override object RequestObject
    {
        get { return Request; }
        set
        {
            if (!(value is T))
            {
                throw new ArgumentException("Invalid type.", "value");
            }
            m_Request = (T)value;
        }
    }
}

//  the non-generic interface
public interface IHPCRequest
{
    event EventHandler<RequestCompleteEventArgs> RequestComplete;
}

//  the generic base class
public abstract class HPCRequest<T> : IHPCRequest 
    where T : HPCRequest<T>
{
    //  this sanitizes the event handler, and makes it callable 
    //  whenever an event handler is subscribed to the non-generic 
    //  interface
    private static EventHandler<RequestCompleteEventArgs<T>> ConvertNonGenericHandler(EventHandler<RequestCompleteEventArgs> handler)
    {
        return (sender, e) => handler(sender, e);
    }

    //  this object is for a lock object for thread safety on the callback event
    private readonly object Bolt = new object();

    //  This determines whether the send method should raise the completed event.
    //  It is false by default, because otherwise you would have issues sending the request asynchronously
    //  without using the SendAsync method.
    public bool AutoRaiseCompletedEvent { get; set; }

    //  This is used to ensure that RequestComplete event cannot fire more than once
    public bool HasRequestCompleteFired { get; private set; }

    //  declare the generic event
    public event EventHandler<RequestCompleteEventArgs<T>> RequestComplete;

    //  explicitly implement the non-generic interface by wiring the the non-generic
    //  event handler to the generic event handler
    event EventHandler<RequestCompleteEventArgs> IHPCRequest.RequestComplete
    {
        add { RequestComplete += ConvertNonGenericHandler(value); }
        remove { RequestComplete -= ConvertNonGenericHandler(value); }
    }

    //  I'm not 100% clear on your intended functionality, but this will call an overrideable send method
    //  then raise the OnRequestCompleted event if the AutoRaiseCompletedEvent property is set to 'true'
    public void Send()
    {
        SendRequest((T)this);

        if(AutoRaiseCompletedEvent) 
        {
            OnRequestCompleted((T)this);
        }
    }

    public void SendAsync()
    {
        // this will make the event fire immediately after the SendRequest method is called
        AutoRaiseCompletedEvent = true;
        new Task(Send).Start();
    }

    //  you can make this virtual instead of abstract if you don't want to require that the Request 
    //  class has the Send implementation
    protected abstract void SendRequest(T request);

    //  this raises the RequestCompleted event if it is the first call to this method.  
    //  Otherwise, an InvalidOperationException is thrown, because a Request can only 
    //  be completed once
    public void OnRequestCompleted(T request)
    {
        bool invalidCall = false;
        Exception handlerException = null;
        if (HasRequestCompleteFired)
            invalidCall = true;
        else
        {
            lock(Bolt)
            {
                if(HasRequestCompleteFired)
                {
                    invalidCall = true;
                }
                else
                {
                    if (RequestComplete != null)
                    {
                        // because you don't want to risk throwing an exception
                        // in a locked context
                        try
                        {
                            RequestComplete(this, new RequestCompleteEventArgs<T> { Request = request });
                        }
                        catch(Exception e)
                        {
                            handlerException = e;
                        }
                    }
                    HasRequestCompleteFired = true;
                }
            }
        }
        if(invalidCall)
        {
            throw new InvalidOperationException("RequestCompleted can only fire once per request");
        }
        if(handlerException != null)
        {
            throw new InvalidOperationException("The RequestComplete handler threw an exception.");
        }
    }


}

// a sample concrete implementation
public class HPCGetConfig : HPCRequest<HPCGetConfig>
{
    protected override void SendRequest(HPCGetConfig request)
    {
        // do some configuration stuff
    }
}
0 голосов
/ 07 марта 2012

Это невозможно сделать .

В итоге мне пришлось разыгрывать делегата везде, где он используется.

Рад изменить принятый ответ, если что-нибудь покажет иначе.

0 голосов
/ 12 ноября 2011

Я думаю, что вам нужно создать абстрактный базовый класс, который может быть не создан, например:

public abstract class HPCRequestBase<T> where T : HPCRequestBase<T>
{
    public delegate void RequestCompleteHandler(T request);
    public event RequestCompleteHandler RequestComplete;

    protected void OnRequestComplete(T request)
    {
        if (RequestComplete != null) {
            RequestComplete(request);
        }
    }

    public void Test( )
    {
        OnRequestComplete((T)this);
    }

}

public class HPCRequest : HPCRequestBase<HPCRequest>
{
    public void Test2()
    {
        OnRequestComplete(this);
    }
}

public class HPCRequestConfig : HPCRequestBase<HPCRequestConfig>
{
    // Derived from the base class, not from HPCRequest 
}

Также 'this' должно быть приведено к T: OnRequestComplete((T)this);

Этот тест выполняется без ошибки:

var hpcr = new HPCRequest();
hpcr.Test();
hpcr.Test2();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...