Вот конечный результат того, что я сделал с Вашим предложением. Я думал, что будет хорошо иметь здесь в случае, если у кого-то есть та же проблема. Получающийся код вызова похож:
content = ReplaceCallback.find(content, regex, new ReplaceCallback.Callback() {
public String matches(MatchResult match) {
// Do something special not normally allowed in regex's...
return "newstring"
}
});
весь список классов следует:
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Stack;
/**
* <p>
* Class that provides a method for doing regular expression string replacement by passing the matched string to
* a function that operates on the string. The result of the operation is then used to replace the original match.
* </p>
* <p>Example:</p>
* <pre>
* ReplaceCallback.find("string to search on", "/regular(expression/", new ReplaceCallback.Callback() {
* public String matches(MatchResult match) {
* // query db or whatever...
* return match.group().replaceAll("2nd level replacement", "blah blah");
* }
* });
* </pre>
* <p>
* This, in effect, allows for a second level of string regex processing.
* </p>
*
*/
public class ReplaceCallback {
public static interface Callback {
public String matches(MatchResult match);
}
private final Pattern pattern;
private Callback callback;
private class Result {
int start;
int end;
String replace;
}
/**
* You probably don't need this. {@see find(String, String, Callback)}
* @param regex The string regex to use
* @param callback An instance of Callback to execute on matches
*/
public ReplaceCallback(String regex, final Callback callback) {
this.pattern = Pattern.compile(regex);
this.callback = callback;
}
public String execute(String string) {
final Matcher matcher = this.pattern.matcher(string);
Stack<Result> results = new Stack<Result>();
while(matcher.find()) {
final MatchResult matchResult = matcher.toMatchResult();
Result r = new Result();
r.replace = callback.matches(matchResult);
if(r.replace == null)
continue;
r.start = matchResult.start();
r.end = matchResult.end();
results.push(r);
}
// Improve this with a stringbuilder...
while(!results.empty()) {
Result r = results.pop();
string = string.substring(0, r.start) + r.replace + string.substring(r.end);
}
return string;
}
/**
* If you wish to reuse the regex multiple times with different callbacks or search strings, you can create a
* ReplaceCallback directly and use this method to perform the search and replace.
*
* @param string The string we are searching through
* @param callback A callback instance that will be applied to the regex match results.
* @return The modified search string.
*/
public String execute(String string, final Callback callback) {
this.callback = callback;
return execute(string);
}
/**
* Use this static method to perform your regex search.
* @param search The string we are searching through
* @param regex The regex to apply to the string
* @param callback A callback instance that will be applied to the regex match results.
* @return The modified search string.
*/
public static String find(String search, String regex, Callback callback) {
ReplaceCallback rc = new ReplaceCallback(regex, callback);
return rc.execute(search);
}
}
Я надеялся получить некоторую документацию, касающуюся производительности в таких случаях, но, похоже, все, что я получил, это предложения о том, как улучшить мой код ... Кажется, никто не читал мой PS - нет баллов за вас.
Итак, я написал простой тестовый пример:
public static bool IsDebuggingEnabled { get; set; }
static void Main(string[] args)
{
for (int j = 0; j <= 10; j++)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i <= 15000; i++)
{
Log(GetDebugMessage);
if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
Console.ReadLine();
for (int j = 0; j <= 10; j++)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i <= 15000; i++)
{
if (IsDebuggingEnabled) GetDebugMessage();
if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
Console.ReadLine();
}
public static string GetDebugMessage()
{
StringBuilder sb = new StringBuilder(100);
Random rnd = new Random();
for (int i = 0; i < 100; i++)
{
sb.Append(rnd.Next(100, 150));
}
return sb.ToString();
}
public static void Log(Func<string> getMessage)
{
if (IsDebuggingEnabled)
{
getMessage();
}
}
Кажется, что время между двумя версиями точно такое же. Я получаю 145 мс в первом случае и 145 мс во втором
Похоже, я ответил на свой вопрос.
Нет, у него не должно быть плохой производительности. В конце концов, вы будете вызывать его только в режиме отладки, где производительность не стоит на первом месте. Фактически, вы можете удалить лямбда и просто передать имя метода , чтобы убрать накладные расходы на ненужный промежуточный анонимный метод.
Обратите внимание, что если вы хотите сделать это в сборках отладки, вы можете добавить [Условный ("DEBUG")]
атрибут метода журнала.
Вы также можете сделать это:
// no need for a lambda
instance.Log(GetDetailedDebugInfo)
// Using these instance methods on the logger
public void Log(Func<string> detailsProvider)
{
if (!DebuggingEnabled)
return;
this.LogImpl(detailsProvider());
}
public void Log(string message)
{
if (!DebuggingEnabled)
return;
this.LogImpl(message);
}
protected virtual void LogImpl(string message)
{
....
}
Стандартные ответы:
Вызов делегата getMessage напрямую вместо вызова Invoke для него.
if(IsDebuggingEnabled)
{
LogInternal(getMessage());
}
Вы также должны добавить нулевую проверку для getMessage.
Я считаю, что делегаты создают новый поток, так что вы можете быть правы насчет повышения производительности. Почему бы не настроить тестовый запуск, как предложил Дав, и внимательно следить за количеством потоков, порождаемых вашим приложением, для этого вы можете использовать Process Explorer.
Подождите! Меня поправили! Делегаты используют потоки только тогда, когда вы используете «BeginInvoke» ... поэтому мои комментарии выше не относятся к тому, как вы их используете.
Есть разница в производительности. Насколько это важно, будет зависеть от остальной части вашего кода, поэтому я бы рекомендовал профилировать, прежде чем приступать к оптимизации.
Сказав это для вашего первого примера:
if (IsDebuggingEnabled)
{
instance.Log(GetDetailedDebugInfo());
}
Если IsDebuggingEnabled статичен только для чтения, тогда проверка будет отклонена, поскольку она знает, что это никогда не изменится. Это означает, что приведенный выше образец не повлияет на производительность, если IsDebuggingEnabled имеет значение false, потому что после завершения JIT код исчезнет.
instance.Log(() => GetDetailedDebugInfo());
public void Log(Func<string> getMessage)
{
if (IsDebuggingEnabled)
{
LogInternal(getMessage.Invoke());
}
}
Метод будет вызываться каждый раз при вызове instance.Log. Что будет медленнее.
Но прежде чем тратить время на эту микрооптимизацию, вы должны профилировать свое приложение или запустить несколько тестов производительности, чтобы убедиться, что это действительно узкое место в вашем приложении.