I have a problem with VB9 and Moq.
I need to call a verify on a Sub. Like so:
logger.Verify(Function(x) x.Log, Times.AtLeastOnce)
And my logger looks like this:
Public Interface ILogger
Sub Log()
End Interface
But with VB this is not possible, because the Log method is a Sub, and thereby does not produce a value.
I don't want to change the method to be a function.
Whats the cleanest way of working around this limitation and is there any way to wrap the Sub as a Function like the below?
logger.Verify(Function(x) ToFunc(AddressOf x.Log), Times.AtLeastOnce)
I have tried this, but i get:
Lambda Parameter not in scope
VB10 allows for the usage of Lambada Subs.
Have you tried a simple wrapper, such as:
Public Function Wrapper(source as Action) as Boolean
source.Invoke()
Return True
End Function
In 2010 if its a Sub and not a Function just replace Function with Sub.
logger.Verify(Sub(x) x.Log, Times.AtLeastOnce)
Related
Using this example from the FluentValidation website, I'm converting the concept to VB.NET using my own classes. The part in interest to my issue is the Must(BeOver18), which calls the protected function. Note that this call doesn't pass a parameter to BeOver18:
public class PersonAgeValidator : AbstractValidator<Person> {
public PersonAgeValidator() {
RuleFor(x => x.DateOfBirth).Must(BeOver18);
}
protected bool BeOver18(DateTime date) {
//...
}
}
I created my own validator class in VB.NET like this, using the same principal as above but instead for a function called CustomerExists:
Public Class ContractValidator
Inherits AbstractValidator(Of ContractDTO)
Public Sub New()
RuleSet("OnCreate",
Sub()
RuleFor(Function(x) x.CustomerID).NotEmpty
' Compiler error here:
' BC30455 Argument not specified for parameter 'customerID'.....
RuleFor(Function(x) x.CustomerID).Must(CustomerExists)
End Sub
)
End Sub
Protected Function CustomerExists(customerID As Integer) As Boolean
Return CustomerService.Exists(customerID)
End Function
End Class
ISSUE: The line in VB.NET with .Must(CustomerExists) is giving the "Argument not specified for parameter 'customerID'..." compiler error. The C# example does not pass a parameter to BeOver18. I tried an additional anonymous in-line function to try to pass ContractDTO.CustomerID, but it doesn't work as it's not recognized:
' This won't work:
RuleFor(Function(x) x.CustomerID).Must(CustomerExists(Function(x) x.CustomerID))
I'm at a loss on how the C# example can call it's function without a parameter, but the VB.NET conversion cannot. This is where i need help.
Your CustomerExists function needs to be treated as a delegate. In order to do that, change the following:
Original
RuleFor(Function(x) x.CustomerID).Must(CustomerExists)
Update
RuleFor(Function(x) x.CustomerID).Must(AddressOf CustomerExists)
I have an Async method that does not end immediately after I close its user control. So, when I close and reopen it very fast, my user control gets errors. How could I exit that Async function from another class. Is that possible?
Public Class Main
Private Sub mainfucn()
'exit otherfunc
End Sub
End Class
Public Class Other
Public Async Function otherfunc() As Task
' some code
End Sub
End Class
Well, you could slum it by implementing your own cancellation system yourself. For instance, probably the simplest way to do something like that would be with some sort of cancellation flag property, like this:
Public Class Main
Private _other As New Other()
Private Sub MainFunc()
other.Cancelled = True
End Sub
End Class
Public Class Other
Public Property Cancelled As Boolean ' Yes, I know I'm not British, but the American spelling of "Canceled" is phonetically stupid. But, then again, so is the spelling of "British", so...
Public Async Function OtherFunc() As Task
Cancelled = False
While Not Cancelled
' Some code
End While
End Sub
End Class
However, that would be pretty terrible, so I wouldn't recommend it. The two primary reasons why it's terrible is because, One, it assumes that there's only ever one Async method and that there's only ever one invocation of it running at a time. And Two, it is inconsistent with the standard async cancellation patterns of .NET.
I would strongly recommend that, instead of attempting to do it with your own (anti-) pattern, you should add a CancellationToken parameter to your Async function. However, the way that you would implement that within the method all depends on what it is doing asynchronously and how it does it. So it's impossible to give you a single good example for how to accomplish that. The best thing I could say is, you were on the right track with using a CancellationToken, so you should keep going down that path. If, after doing more research, you aren't able to get it working, then I would recommend posting a more specific question regarding how to implement a CancellationToken within the context of what your method is doing and how it operates.
I have the follow class in vb6:
Public Function NewEnum()
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
NewEnum = mcolFields.[_NewEnum]
End Function
What would the equivalent attributes be in vb.net? I know that you have to put attributes in <>and I also found this SO post, however it didn't solve my problem.
GetEnumerator() is the exact equivalent. It gets exposed as NewEnum in <ComVisible(True)> code. Simply implement the System.Collections.IEnumerable interface, the non-generic one.
Some info about this is here: https://christopherjmcclellan.wordpress.com/2015/04/21/vb-attributes-what-are-they-and-why-should-we-use-them/
There is one more special value for VB_UserMemId and that value is -4.
Negative 4 always indicates that the function being marked should
return a [_NewEnum] enumerator.
I would say that in this case you can ignore them. So your equivalent should be something like this:
Public Function NewEnum() As mcolFields
Return New mcolFields
End Function
Having sample trivial class
Class A
Property Property1 As Integer = 5
Sub Action1()
Debug.Print(Property1.ToString())
End Sub
End Class
I can always call Action1() like
Dim instanceA As New A
instanceA.Action1()
But can I call the method without using the variable? Something like
(New A).Action1()
I'm getting syntax error at the 1st character when attempting that.
The reason that you get a syntax error is that a line of VB code cannot begin with the New keyword. I find that the best way around that is to use the otherwise useless Call keyword:
Call New A().Action1()
I want to format any numeric type using a method call like so:
Option Infer On
Option Strict Off
Imports System.Runtime.CompilerServices
Namespace GPR
Module GPRExtensions
<Extension()>
Public Function ToGPRFormattedString(value) As String
' Use VB's dynamic dispatch to assume that value is numeric
Dim d As Double = CDbl(value)
Dim s = d.ToString("N3")
Dim dynamicValue = value.ToString("N3")
Return dynamicValue
End Function
End Module
End Namespace
Now, from various discussions around the web (VB.Net equivalent for C# 'dynamic' with Option Strict On, Dynamic Keyword equivalent in VB.Net?), I would think that this code would work when passed a numeric type (double, Decimal, int, etc). It doesn't, as you can see in the screenshot:
I can explicitly convert the argument to a double and then .ToString("N3") works, but just calling it on the supposedly-dynamic value argument fails.
However, I can do it in C# with the following code (using LINQPad). (Note, the compiler won't let you use a dynamic parameter in an extension method, so maybe that is part of the problem.)
void Main()
{
Console.WriteLine (1.ToGPRFormattedString());
}
internal static class GPRExtensions
{
public static string ToGPRFormattedString(this object o)
{
// Use VB's dynamic dispatch to assume that value is numeric
var value = o as dynamic;
double d = Convert.ToDouble(value);
var s = d.ToString("N3").Dump("double tostring");
var dynamicValue = value.ToString("N3");
return dynamicValue;
}
}
So what gives? Is there a way in VB to call a method dynamically on an argument to a function without using reflection?
To explicitly answer "Is there a way in VB to call a method dynamically on an argument to a function without using reflection?":
EDIT: I've now reviewed some IL disassembly (via LinqPad) and compared it to the code of CallByName (via Reflector) and using CallByName uses the same amount of Reflection as normal, Option Strict Off late binding.
So, the complete answer is: You can do this with Option Strict Off for all Object references, except where the method you're trying exists on Object itself, where you can use CallByName to get the same effect (and, in fact, that doesn't need Option Strict Off).
Dim dynamicValue = CallByName(value, "ToString", CallType.Method, "N3")
NB This is not actually the equivalent to the late binding call, which must cater for the possibility that the "method" is actually a(n indexed) property, so it actually calls the equivalent of:
Dim dynamicValue = CallByName(value, "ToString", CallType.Get, "N3")
for other methods, like Double.CompareTo.
Details
Your problem here is that Object.ToString() exists and so your code is not attempting any dynamic dispatch, but rather an array index lookup on the default String.Chars property of the String result from that value.ToString() call.
You can confirm this is what is happening at compile time by trying value.ToString(1,2), which you would prefer to attempt a runtime lookup for a two parameter ToString, but in fact fails with
Too many arguments to 'Public ReadOnly Default Property Chars(index As Integer) As Char'
at compile time.
You can similarly confirm all other non-Shared Object methods are called directly with callvirt, relying upon Overrides where available, not dynamic dispatch with calls to the Microsoft.VisualBasic.CompilerServices.NewLateBinding namespace, if you review the compiled code in IL.
You are using a lot of implicit typing, and the compiler doesn't appear to be assigning the type System.Dynamic to the variables you want to be dynamic.
You could try something like:
Option Infer On
Option Strict Off
Imports System.Runtime.CompilerServices
Namespace GPR
Module GPRExtensions
<Extension()>
Public Function ToGPRFormattedString(value as System.Dynamic) As String
' Use VB's dynamic dispatch to assume that value is numeric
Dim d As Double = CDbl(value)
Dim s = d.ToString("N3")
Dim dynamicValue as System.Dynamic = value.ToString("N3")
Return dynamicValue
End Function
End Module
End Namespace
Option Infer On
Option Strict Off
Imports System.Runtime.CompilerServices
Namespace GPR
Module GPRExtensions
<Extension()>
Public Function ToGPRFormattedString(value) As String
Dim dynamicValue = FormatNumber(CDbl(value), 3)
Return dynamicValue
End Function
End Module
End Namespace