public void Add(Type commonType, BinaryCallTarget implementation, TypeConverter resultConverter) { TypePair key = new TypePair(commonType, commonType); DispatchRecord rec = new DispatchRecord(key, commonType, null, null, resultConverter, implementation); Add(rec); }
public void Evaluate(EvaluationContext context) { DispatchRecord record = null; try { Type arg1Type = (context.Arg1 == null ? null : context.Arg1.GetType()); Type arg2Type = (context.Arg2 == null ? null : context.Arg2.GetType()); TypePair key = new TypePair(arg1Type, arg2Type); //FIND RECORD for types - remember threads!!! We don't use any locks here - see below Add(record) method. if (!DispatchRecords.TryGetValue(key, out record)) { record = this.Runtime.CreateDispatchRecord(this, key); } context.CurrentResult = record.Evaluate(context.Arg1, context.Arg2); } catch (OverflowException ex) { if (this.Runtime.HandleOverflow(ex, this, record, context)) { Evaluate(context); //recursively call self again, with new arg types } } catch (Exception ex) { if (!this.Runtime.HandleException(ex, this, record, context)) { throw; } } }
//This is just a sketch //TODO: implement customizable behavior, using dictionary type->type, to specify to which type to switch in case of overflow public virtual bool HandleOverflow(Exception ex, CallDispatcher dispatcher, DispatchRecord failedRecord, EvaluationContext context) { //get the common type and decide what to do... Type newType = null; switch (failedRecord.CommonType.Name) { case "Byte": case "SByte": case "Int16": case "UInt16": case "Int32": case "UInt32": newType = typeof(Int64); break; case "Int64": case "UInt64": newType = typeof(BigInteger); break; case "Single": newType = typeof(double); break; } if (newType == null) { throw ex; } context.CallArgs[0] = Convert.ChangeType(context.CallArgs[0], newType); context.CallArgs[1] = Convert.ChangeType(context.CallArgs[1], newType); return(true); }
// Thread safety. We don't want to slow down operator evaluation, so in Evaluate method // we call DispatchRecords.TryGetValue without any locks. // However, we need to update the DispatchRecords table from time to time // adding records for new combinations of arg types. // Here's how we do it. We maintain two identical copies of DispatchRecords table: primary and clone. // When we need to add a record, we first add it to the clone; // next, in atomic operation (using Interlocked) we swap primary and clone tables. // Finally, we add the new record to the clone table, which is the former primary. #endregion internal void Add(DispatchRecord record) { lock (this) { //lock the whole operation DispatchRecordsClone[record.Key] = record; //add the record to clone //swap primary and clone tables in atomic operation //disable message "a reference to a volatile field will not be treated as volatile"; interlocked operations are exceptions #pragma warning disable 0420 DispatchRecordsClone = System.Threading.Interlocked.Exchange(ref DispatchRecords, DispatchRecordsClone); DispatchRecordsClone[record.Key] = record; //update new clone/former primary } }
public virtual DispatchRecord CreateDispatchRecord(CallDispatcher dispatcher, TypePair forKey) { Type commonType = GetCommonType(dispatcher.MethodName, forKey.Arg1Type, forKey.Arg2Type); if (commonType == null) { return(null); } TypeConverter arg1Converter = GetConverter(forKey.Arg1Type, commonType); TypeConverter arg2Converter = GetConverter(forKey.Arg2Type, commonType); //Get base method for the operator and common type TypePair baseKey = new TypePair(commonType, commonType); DispatchRecord rec = dispatcher.GetRecord(baseKey); if (rec == null) { throw new RuntimeException("Operator or method " + dispatcher.MethodName + " is not defined for type " + commonType); } rec = new DispatchRecord(forKey, commonType, arg1Converter, arg2Converter, rec.ResultConverter, rec.Implementation); dispatcher.Add(rec); return(rec); }
public virtual bool HandleException(Exception ex, CallDispatcher dispatcher, DispatchRecord failedTarget, EvaluationContext context) { return(false); }