2009-06-16 34 views
33

Tôi có một từ điển để ánh xạ một loại nhất định cho một đối tượng chung nhất định cho loại đó. Ví dụ:Truyền sang loại chung trong C#

typeof(LoginMessage) maps to MessageProcessor<LoginMessage> 

Bây giờ, vấn đề là truy xuất đối tượng chung này khi chạy từ điển. Hoặc cụ thể hơn: Để truyền đối tượng đã truy xuất đến loại chung chung cụ thể.

tôi cần nó để làm việc gì đó như thế này:

Type key = message.GetType(); 
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>; 

Hope có một giải pháp dễ dàng cho việc này.

Chỉnh sửa: Tôi không muốn sử dụng If và switch. Do các vấn đề hiệu suất, tôi không thể sử dụng sự phản chiếu của một số loại nào đó.

+0

câu hỏi sau có một kịch bản đúc tránh sử dụng Generics - [Refactoring Mã để tránh Loại Đúc] (http://stackoverflow.com/questions/21482850/refactoring-code-to-avoid-type-casting) – Lijo

Trả lời

29

Điều này làm việc cho bạn?

interface IMessage 
{ 
    void Process(object source); 
} 

class LoginMessage : IMessage 
{ 
    public void Process(object source) 
    { 
    } 
} 

abstract class MessageProcessor 
{ 
    public abstract void ProcessMessage(object source, object type); 
} 

class MessageProcessor<T> : MessageProcessor where T: IMessage 
{ 
    public override void ProcessMessage(object source, object o) 
    { 
     if (!(o is T)) { 
      throw new NotImplementedException(); 
     } 
     ProcessMessage(source, (T)o); 
    } 

    public void ProcessMessage(object source, T type) 
    { 
     type.Process(source); 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     Dictionary<Type, MessageProcessor> messageProcessors = new Dictionary<Type, MessageProcessor>(); 
     messageProcessors.Add(typeof(string), new MessageProcessor<LoginMessage>()); 
     LoginMessage message = new LoginMessage(); 
     Type key = message.GetType(); 
     MessageProcessor processor = messageProcessors[key]; 
     object source = null; 
     processor.ProcessMessage(source, message); 
    } 
} 

Điều này cung cấp cho bạn đối tượng chính xác. Điều duy nhất tôi không chắc chắn về việc liệu nó là đủ trong trường hợp của bạn để có nó như là một MessageProcessor trừu tượng.

Chỉnh sửa: Tôi đã thêm giao diện IMessage. Mã xử lý thực tế bây giờ sẽ trở thành một phần của các lớp thông báo khác nhau mà tất cả nên thực hiện giao diện này.

+0

Không, bởi vì tôi cần một cá thể chung chung có phương pháp chung kiểu an toàn. Đáng buồn là lớp cơ sở sẽ không làm điều này;) –

+0

Tôi đã thêm một ví dụ về cách bạn có thể thêm một phương thức trừu tượng vào lớp trừu tượng và ghi đè nó trong lớp bê tông. –

+0

Tôi hy vọng tránh kiểm tra kiểu khi chạy nhưng như mọi người đã nói, có vẻ như không thể tránh khỏi. ;) Vì vậy, tôi sẽ phải đi theo cách tiếp cận này. Cảm ơn. –

4

Bạn không thể làm điều đó. Bạn có thể thử nói với vấn đề của bạn từ một quan điểm cấp cao hơn (tức là chính xác bạn muốn đạt được gì với biến được đúc) cho một giải pháp khác.

Bạn có thể đi với một cái gì đó như thế này:

public abstract class Message { 
    // ... 
} 
public class Message<T> : Message { 
} 

public abstract class MessageProcessor { 
    public abstract void ProcessMessage(Message msg); 
} 
public class SayMessageProcessor : MessageProcessor { 
    public override void ProcessMessage(Message msg) { 
     ProcessMessage((Message<Say>)msg); 
    } 
    public void ProcessMessage(Message<Say> msg) { 
     // do the actual processing 
    } 
} 

// Dispatcher logic: 
Dictionary<Type, MessageProcessor> messageProcessors = { 
    { typeof(Say), new SayMessageProcessor() }, 
    { typeof(string), new StringMessageProcessor() } 
}; // properly initialized 

messageProcessors[msg.GetType().GetGenericArguments()[0]].ProcessMessage(msg); 
+0

Nếu điều này là không thể, có cách nào khác để ánh xạ một loại cho một loại chung nhất định không? –

+0

@Andrej: "bản đồ" hơi mơ hồ ở đây. Bạn có thể thử nói cho chúng tôi biết bạn muốn làm gì với biến được đúc không? Vì kiểu không biết lúc biên dịch, bạn không thể gọi các phương thức trên hay không, trừ khi bạn mã hóa các loại 'khóa' khác nhau ... –

+0

Tôi có một số Thư (LoginMessage, SayMessage, ...). Mỗi Message phải được xử lý bởi một MessageProcessor cụ thể có một phương thức như "ProcessMessage (nguồn MessageSource, MessageType message)". Tất nhiên tôi có thể sử dụng lớp cơ sở của các Tin nhắn thay vì cách tiếp cận chung, nhưng tôi muốn điều này được an toàn để một Bộ xử lý cụ thể chỉ có thể xử lý thông điệp mà anh ta phải làm. –

7
Type type = typeof(MessageProcessor<>).MakeGenericType(key); 

Đó là tốt nhất bạn có thể làm, tuy nhiên mà không thực sự biết những gì loại đó là, có thực sự không nhiều hơn nữa bạn có thể làm gì với nó.

CHỈNH SỬA: Tôi nên làm rõ. Tôi đã thay đổi từ loại var thành Type type. Quan điểm của tôi là, bây giờ bạn có thể làm một cái gì đó như thế này:

object obj = Activator.CreateInstance(type); 

tại obj sẽ đúng loại, nhưng kể từ khi bạn không biết những gì kiểu "chìa khóa" là tại thời gian biên dịch, không có cách nào để cast nó và làm bất cứ điều gì hữu ích với nó.

+1

Sẽ không hoạt động. 'var' chỉ là cú pháp suger được giải quyết tại thời gian biên dịch. Kiểu 'type' sẽ là' System.Type'. –

+0

Vâng, tôi biết điều đó. Nó sẽ là loại MessageProcessor mà sau đó bạn có thể sử dụng với Activator.CreateInstance(). Quan điểm của tôi là, mặc dù vào thời điểm đó, bạn không thể làm gì nhiều với nó bởi vì bạn không biết loại "chìa khóa" là gì. – BFree

1

Như đã đề cập, bạn không thể truyền trực tiếp. Một giải pháp có thể là có những kiểu generic này kế thừa từ một giao diện không chung chung, trong trường hợp này bạn vẫn có thể gọi các phương thức trên nó mà không cần phản ánh. Sử dụng sự phản chiếu, bạn có thể truyền đối tượng được ánh xạ tới bất kỳ phương thức nào mong đợi nó, sau đó diễn viên sẽ được thực hiện cho bạn. Vì vậy, nếu bạn có một phương pháp được gọi là Chấp nhận mong đợi một MessageProcessor như một tham số, sau đó bạn có thể tìm thấy nó và gọi nó tự động.

+0

Mã này có thể là hiệu suất quan trọng, vì vậy tôi muốn tránh sử dụng các phương pháp phản chiếu hoặc tương tự. –

2

này chỉ đơn giản là không được phép:

Type key = message.GetType(); 
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>; 

Bạn không thể có được một kiểu generic như một giá trị biến.

Bạn sẽ phải làm một switch hoặc một cái gì đó:

Type key = message.GetType(); 
if (key == typeof(Foo)) 
{ 
    MessageProcessor<Foo> processor = (MessageProcessor<Foo>)messageProcessors[key]; 
    // Do stuff with processor 
} 
else if (key == typeof(Bar)) 
{ 
    MessageProcessor<bar> processor = (MessageProcessor<Bar>)messageProcessors[key]; 
    // Do stuff with processor 
} 
... 
+0

Tôi sử dụng cấu trúc này để tránh chính xác Ifs và switch. Vì vậy, đây là một không đi. –

+1

Sau đó, đặt câu hỏi đó vào câu hỏi của bạn. –

8

Bạn có thể viết một phương pháp mà có loại như một tham số chung:

void GenericProcessMessage<T>(T message) 
{ 
    MessageProcessor<T> processor = messageProcessors[typeof(T)] 
     as MessageProcessor<T>; 

    // Call method processor or whatever you need to do 
} 

Sau đó, bạn cần một cách để gọi các phương pháp với lập luận chung đúng. Bạn có thể làm điều này với sự phản ánh:

public void ProcessMessage(object message) 
{ 
    Type messageType = message.GetType(); 
    MethodInfo method = this.GetType().GetMethod("GenericProcessMessage"); 
    MethodInfo closedMethod = method.MakeGenericMethod(messageType); 
    closedMethod.Invoke(this, new object[] {message}); 
} 
+0

Như đã đề cập, do các vấn đề hiệu suất tôi muốn tránh sử dụng sự phản chiếu. Những phương pháp này có thể được gọi là hundrets của thời gian trong thời gian rất ngắn và vẫn cần phải thực hiện rất nhanh. –

+1

Để tối ưu hóa điều này, bạn có thể thiết lập để phản ánh chỉ được thực hiện một lần cho mỗi loại thông báo. Bạn có thể tạo một đại biểu gọi các phương thức chung và lưu trữ nó trong một từ điển, vì vậy trong lần tiếp theo, loại đó xuất hiện, nó chỉ cần gọi đại biểu. Cách dễ nhất để làm điều này có lẽ là bằng cách biên dịch một biểu thức lambda. –

+0

Một lần nữa, Andrej, một cái gì đó để đưa vào câu hỏi của bạn. –

4

Vui lòng xem giải pháp sau đây có phù hợp với bạn hay không. Bí quyết là xác định một giao diện bộ vi xử lý cơ sở mà lấy một kiểu cơ bản của thông báo.

interface IMessage 
{ 
} 

class LoginMessage : IMessage 
{ 
} 

class LogoutMessage : IMessage 
{ 
} 

class UnknownMessage : IMessage 
{ 
} 

interface IMessageProcessor 
{ 
    void PrcessMessageBase(IMessage msg); 
} 

abstract class MessageProcessor<T> : IMessageProcessor where T : IMessage 
{ 
    public void PrcessMessageBase(IMessage msg) 
    { 
     ProcessMessage((T)msg); 
    } 

    public abstract void ProcessMessage(T msg); 

} 

class LoginMessageProcessor : MessageProcessor<LoginMessage> 
{ 
    public override void ProcessMessage(LoginMessage msg) 
    { 
     System.Console.WriteLine("Handled by LoginMsgProcessor"); 
    } 
} 

class LogoutMessageProcessor : MessageProcessor<LogoutMessage> 
{ 
    public override void ProcessMessage(LogoutMessage msg) 
    { 
     System.Console.WriteLine("Handled by LogoutMsgProcessor"); 
    } 
} 

class MessageProcessorTest 
{ 
    /// <summary> 
    /// IMessage Type and the IMessageProcessor which would process that type. 
    /// It can be further optimized by keeping IMessage type hashcode 
    /// </summary> 
    private Dictionary<Type, IMessageProcessor> msgProcessors = 
           new Dictionary<Type, IMessageProcessor>(); 
    bool processorsLoaded = false; 

    public void EnsureProcessorsLoaded() 
    { 
     if(!processorsLoaded) 
     { 
      var processors = 
       from processorType in Assembly.GetExecutingAssembly().GetTypes() 
       where processorType.IsClass && !processorType.IsAbstract && 
         processorType.GetInterface(typeof(IMessageProcessor).Name) != null 
       select Activator.CreateInstance(processorType); 

      foreach (IMessageProcessor msgProcessor in processors) 
      { 
       MethodInfo processMethod = msgProcessor.GetType().GetMethod("ProcessMessage"); 
       msgProcessors.Add(processMethod.GetParameters()[0].ParameterType, msgProcessor); 
      } 

      processorsLoaded = true; 
     } 
    } 

    public void ProcessMessages() 
    { 
     List<IMessage> msgList = new List<IMessage>(); 
     msgList.Add(new LoginMessage()); 
     msgList.Add(new LogoutMessage()); 
     msgList.Add(new UnknownMessage()); 

     foreach (IMessage msg in msgList) 
     { 
      ProcessMessage(msg); 
     } 
    } 

    public void ProcessMessage(IMessage msg) 
    { 
     EnsureProcessorsLoaded(); 
     IMessageProcessor msgProcessor = null; 
     if(msgProcessors.TryGetValue(msg.GetType(), out msgProcessor)) 
     { 
      msgProcessor.PrcessMessageBase(msg); 
     } 
     else 
     { 
      System.Console.WriteLine("Processor not found"); 
     } 
    } 

    public static void Test() 
    { 
     new MessageProcessorTest().ProcessMessages(); 
    } 
} 
6

Tôi gặp sự cố tương tự. Tôi có một lớp học;

Action<T> 

trong đó có một loại tài sản của T.

Làm thế nào để có được tài sản khi tôi không biết T? Tôi không thể đúc to Action <> trừ khi tôi biết T.

SOLUTION:

Thực hiện một giao diện không chung;

public interface IGetGenericTypeInstance 
{ 
    object GenericTypeInstance(); 
} 

Bây giờ tôi có thể đưa đối tượng vào IGetGenericTypeInstance và GenericTypeInstance sẽ trả về thuộc tính làm đối tượng kiểu.

+0

Bạn là người đàn ông của tôi, là một thiên tài, upvotes tất cả các xung quanh! –

13

Sau đây dường như làm việc là tốt, và nó ngắn hơn các câu trả lời khác một chút:

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T)); 
+1

Nếu có lý do cụ thể nào được bỏ phiếu, tôi muốn biết lý do tại sao - dù đây là thực hành không tốt, không hoạt động hay điều gì khác? – ptrc

+2

Tôi đoán điều này đã bị bỏ phiếu vì điều này chắc chắn là sai. Chuyển đổi không được truyền. http://msdn.microsoft.com/en-us/library/dtb69x08.aspx Nó chỉ hoạt động nếu bạn thực hiện IConvertible và như bạn có thể thấy, IConvertible chỉ định nghĩa chuyển đổi thành các loại CLR: http://msdn.microsoft.com/ vi/us/library/system.iconvertible.aspx –

+4

Ah, tôi không hiểu sự khác biệt, chỉ biết nó làm việc cho tôi. Cảm ơn vì đã giải thích. – ptrc

0

Câu trả lời của @DanielPlaisted trước thường hoạt động, nhưng phương pháp chung phải được công khai hoặc người ta phải sử dụng BindingFlags.NonPublic | BindingFlags.Instance! Không thể đăng nó dưới dạng nhận xét vì thiếu danh tiếng.

1
public delegate void MessageProcessor<T>(T msg) where T : IExternalizable; 


    virtual public void OnRecivedMessage(IExternalizable msg) 
    { 
     Type type = msg.GetType(); 
     ArrayList list = processors.Get(type); 
     if (list != null) 
     { 
      object[] args = new object[]{msg}; 
      for (int i = list.Count - 1; i >= 0; --i) 
      { 
       Delegate e = (Delegate)list[i]; 
       e.Method.Invoke(e.Target, args); 
      } 
     } 
    } 
+0

Xin chào! Chào mừng bạn đến với trang web! Bạn có nhớ mô tả những gì giải pháp của bạn làm để làm cho nó rõ ràng hơn? – tjons

0

Tôi cố gắng giải quyết vấn đề tương tự xung quanh lớp bảng dữ liệu thay vì thư. Các vấn đề gốc được đề cập ở trên của đúc một phiên bản không chung chung của lớp để một phiên bản chung có nguồn gốc là như nhau.

Để cho phép tiêm vào thư viện lớp di động không hỗ trợ thư viện cơ sở dữ liệu, tôi đã giới thiệu một tập hợp các lớp giao diện, với mục đích tôi có thể vượt qua một loại và nhận được kết hợp chung. Nó đã kết thúc cần phải thực hiện một phương pháp chung chung.

// Interface for injection 
public interface IDatabase 
{ 
    // Original, non-functional signature: 
    IDatatable<object> GetDataTable(Type dataType); 

    // Functional method using a generic method: 
    IDatatable<T> GetDataTable<T>(); 
} 

Và điều này thực hiện toàn bộ bằng cách sử dụng phương pháp chung ở trên.

Lớp chung sẽ được truyền từ từ điển.

// Non-generic base class allows listing tables together 
abstract class Datatable 
{ 
    Datatable(Type storedClass) 
    { 
     StoredClass = storedClass; 
    } 

    Type StoredClass { get; private set; } 
} 

// Generic inheriting class 
abstract class Datatable<T>: Datatable, IDatatable<T> 
{ 
    protected Datatable() 
     :base(typeof(T)) 
    { 
    } 
} 

Đây là lớp học mà các cửa hàng lớp generic và phôi nó để đáp ứng các phương pháp chung trong giao diện

class Database 
{ 
    // Dictionary storing the classes using the non-generic base class 
    private Dictionary<Type, Datatable> _tableDictionary; 

    protected Database(List<Datatable> tables) 
    { 
     _tableDictionary = new Dictionary<Type, Datatable>(); 
     foreach (var table in tables) 
     { 
      _tableDictionary.Add(table.StoredClass, table); 
     } 
    } 

    // Interface implementation, casts the generic 
    public IDatatable<T> GetDataTable<T>() 
    { 
     Datatable table = null; 

     _tableDictionary.TryGetValue(typeof(T), out table); 

     return table as IDatatable<T>; 
    } 
} 

Và cuối cùng sự kêu gọi của phương pháp giao diện.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>(); 
Các vấn đề liên quan